{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a28ac450-fd0d-44d2-a4ab-00266c4ea1cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c7423b6-b2fd-4898-91f0-c8dc278c7079",
   "metadata": {},
   "source": [
    "# 加载数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "555538ae-32a5-4066-9318-fdcabbfb2729",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to data/MNIST/raw/train-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 9912422/9912422 [04:46<00:00, 34642.19it/s] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/train-images-idx3-ubyte.gz to data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz to data/MNIST/raw/train-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 28881/28881 [00:00<00:00, 112644.86it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/train-labels-idx1-ubyte.gz to data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz to data/MNIST/raw/t10k-images-idx3-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1648877/1648877 [00:01<00:00, 842675.05it/s] \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/t10k-images-idx3-ubyte.gz to data/MNIST/raw\n",
      "\n",
      "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz\n",
      "Failed to download (trying next):\n",
      "HTTP Error 404: Not Found\n",
      "\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz\n",
      "Downloading https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz to data/MNIST/raw/t10k-labels-idx1-ubyte.gz\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4542/4542 [00:00<00:00, 1463511.47it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting data/MNIST/raw/t10k-labels-idx1-ubyte.gz to data/MNIST/raw\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# Download training data from open datasets.\n",
    "training_data = datasets.MNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor(),\n",
    ")\n",
    "\n",
    "# Download test data from open datasets.\n",
    "test_data = datasets.MNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor(),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a5ac0d0e-ff20-4738-954b-59a9702d562f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])\n",
      "Shape of y: torch.Size([64]) torch.int64\n"
     ]
    }
   ],
   "source": [
    "batch_size = 64\n",
    "\n",
    "# Create data loaders.\n",
    "train_dataloader = DataLoader(training_data, batch_size=batch_size)\n",
    "test_dataloader = DataLoader(test_data, batch_size=batch_size)\n",
    "\n",
    "for X, y in test_dataloader:\n",
    "    print(f\"Shape of X [N, C, H, W]: {X.shape}\")\n",
    "    print(f\"Shape of y: {y.shape} {y.dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21ba249a-4a7b-4c0e-99d0-91630231fc8e",
   "metadata": {},
   "source": [
    "# 自编码器"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26019200-f650-4463-ab14-c853fad24a70",
   "metadata": {},
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "class Autoencoder(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Autoencoder, self).__init__()\n",
    "        \n",
    "        # 编码器\n",
    "        self.encoder = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 64),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(64, 12),  # 压缩到低维表示\n",
    "            nn.ReLU(True)\n",
    "        )\n",
    "        \n",
    "        # 解码器\n",
    "        self.decoder = nn.Sequential(\n",
    "            nn.Linear(12, 64),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(64, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 28 * 28),\n",
    "            nn.Sigmoid()  # 输出归一化像素值 [0,1]\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.encoder(x)\n",
    "        x = self.decoder(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "6447ad52-7cd3-4c93-8120-2e9dc9357d0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "class Autoencoder(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Autoencoder, self).__init__()\n",
    "        \n",
    "        # 编码器\n",
    "        self.encoder = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 12),  # 压缩到低维表示\n",
    "            nn.ReLU(True)\n",
    "        )\n",
    "        \n",
    "        # 解码器\n",
    "        self.decoder = nn.Sequential(\n",
    "            nn.Linear(12, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 128),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(128, 28 * 28),\n",
    "            nn.Sigmoid()  # 输出归一化像素值 [0,1]\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.encoder(x)\n",
    "        x = self.decoder(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "132edbda-f25e-481f-8f6d-5b052fae4e08",
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "model = Autoencoder().to(device)\n",
    "criterion = nn.MSELoss()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8655da-9933-430d-8192-e65481420ef7",
   "metadata": {},
   "source": [
    "# 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "28a78d5b-e162-4892-9526-1ca1e3017df4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [1/10], Loss: 0.0608\n",
      "Epoch [2/10], Loss: 0.0432\n",
      "Epoch [3/10], Loss: 0.0359\n",
      "Epoch [4/10], Loss: 0.0325\n",
      "Epoch [5/10], Loss: 0.0308\n",
      "Epoch [6/10], Loss: 0.0291\n",
      "Epoch [7/10], Loss: 0.0278\n",
      "Epoch [8/10], Loss: 0.0269\n",
      "Epoch [9/10], Loss: 0.0262\n",
      "Epoch [10/10], Loss: 0.0256\n"
     ]
    }
   ],
   "source": [
    "num_epochs = 10\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    total_loss = 0\n",
    "    for data, _ in train_dataloader:\n",
    "        img = data.view(data.size(0), -1).to(device)  # 展平为784维\n",
    "        recon = model(img)\n",
    "        loss = criterion(recon, img)\n",
    "        \n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        total_loss += loss.item()\n",
    "\n",
    "    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss / len(train_dataloader):.4f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "949c5e15-c61e-48d4-9427-947d74003d83",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<All keys matched successfully>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.load_state_dict(torch.load('autoencoder_mnist.pth'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "92f4635e-2145-429b-aaa4-c298c5afb531",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Pp/G7ZFHHsGHP/xhdHV1wWq14q677kIymUSxWMQDDzyAjo4OWCwWvOMd71j0XQIrm48Ms55gzyvTUvznf/4n+vv7hfHRyCtf+Ur09/cv2gQA4O6778bIyAj+/u//fpEBoeTtb387HnnkEbzlLW/BK17xCjz99NN4zWtes+JrHB0dxV133YV7770Xb3vb2/CVr3wFb3/723HppZdi69atAE5sXI8++ijuvvtuDAwMIBwO48tf/jKuu+46HDx4EJ2dnSv+PAB48cUXUSqVcMkll5zR7z3++OO49dZbMTg4iIceegj5fB5f+MIXcPXVV+Oll16qMzwA4A1veAP6+/vxiU98As899xw+//nPIx6PLzLOL730UsiyjGeffRa33Xbbkp/t8/lw3XXX4ZFHHsGHPvShuue++93vQq1WC8PgoYcewic+8Qm8613vwq5du5BKpfDCCy/gpZdeOitPGRlkTqdTPBYOh/GKV7xCGJNerxc/+9nPcO+99yKVSuGBBx4AcCJd5LbbbsMTTzyBN77xjXj/+9+PdDqNxx57DPv378fQ0BBkWcbtt9+OJ598Evfeey927tyJX/ziF3jwwQcxMzODz33uc3XX88wzz+CHP/wh7rvvPlitVnz+85/H61//ekxNTcHtdgMA3vOe9+D73/8+3ve+92HLli2IRqN45plncOjQIVxyySX44Ac/iGQyiWAwKN7fYrHUfc5HP/pR6HQ6/NVf/RWKxSJ0Ot0ZfW/kvdu6dSv+5//8n3A4HHj55Zfx85//HG9605tOeQ21Wg233347nnnmGbz73e/G5s2bsW/fPnzuc5/D0aNH8eijj4rPede73oVvfvObeNOb3oSrrroKv/rVr1Y8B2dmZjA1NbXsXKhWq4hEIgBOHIoOHTokDOqrr75avO7AgQO49tprYbPZ8Nd//dfQarX48pe/jOuvvx5PP/00rrjiCrzyla/E/fffj89//vP4X//rf2Hz5s0AgM2bN+Phhx8W+ecf/OAHAZy454ET99pVV12FXC6H+++/H263G1//+tdx++234/vf//6itKdPfOITMBqN+Ju/+RuMjo7iC1/4ArRaLVQqFeLxOB566CE899xz+NrXvoaBgQH83d/9Xd3vr2Q+Msy6QmaYFiGRSMgA5Ne97nWnfN3tt98uA5BTqZQsy7L8oQ99SAYg33PPPYteS88RL774ogxAfuCBB+pe9/a3v10GIH/oQx8Sj331q1+VAcjj4+Pisb6+PhmA/Otf/1o8Nj8/L+v1evkDH/iAeKxQKMjVarXuM8bHx2W9Xi9/5CMfqXsMgPzVr371lP/nf/mXf5EByPv27Vv2NQsLC4v+Dzt37pQ7OjrkaDQqHtuzZ4+sUqnkt771reIx+p5uv/32uve87777ZADynj176h6fnZ2VAcif+tSnTnndX/7yl5e87i1btsivetWrxL937Nghv+Y1rznley3Fk08+KQOQv/KVr8gLCwvy7Oys/POf/1weHh6WJUmSf//734vX3nvvvXIgEJAjkUjde7zxjW+U7Xa7nMvlZFmW5a985SsyAPmzn/3sos+r1WqyLMvyo48+KgOQP/axj9U9f9ddd8mSJMmjo6PiMQCyTqere2zPnj0yAPkLX/iCeMxut8vvfe97T/n/fc1rXiP39fUt+z0MDg6K/wfROAeIxvs7kUjIVqtVvuKKK+R8Pr/k//tU1/Bv//Zvskqlkn/zm9/UPf6lL31JBiD/93//tyzLsrx7924ZgHzffffVve5Nb3rTovt3KR5//HEZgPyf//mfi5677rrrZACLfjZv3iyPjY3VvfaOO+6QdTqdfPz4cfHY7OysbLVa5Ve+8pXise9973syAPnJJ59c9Hlbt26Vr7vuukWPP/DAAzKAuu8inU7LAwMDcn9/v1gbaNy2bdsml0ol8dp77rlHliRJvvXWW+ve98orr1zyu1/pfGSY9QKnDTAtQzqdBgBYrdZTvo6eT6VSdY+/5z3vOe1nUKj2vvvuq3v8z//8z1d8nVu2bKnzDHu9XmzcuBFjY2PiMb1eL3INq9UqotEoLBYLNm7cKMLAZ0I0GgVQ70k8HaFQCLt378bb3/52uFwu8fj27dvx6le/Gj/96U8X/c573/veun/T99L4WroO8nAtx5133gmNRoPvfve74rH9+/fj4MGD+OM//mPxmMPhwIEDB3Ds2LEV/u/qeec73wmv14vOzk78wR/8AZLJJP7t3/4Nl19+OQBAlmX84Ac/wGtf+1rIsoxIJCJ+brnlFiSTSTEuP/jBD+DxeJa8JyhM/dOf/hRqtRr3339/3fMf+MAHIMsyfvazn9U9ftNNN2FoaEj8e/v27bDZbHX3jMPhwO9+9zvMzs6e1XcAAG9729tgNBrP6ncfe+wxpNNp/M3f/M2iAseVhOe/973vYfPmzdi0aVPd9/uqV70KAPDkk08COHkvNX535Pk+HaebC/39/Xjsscfw2GOP4Wc/+xkefvhhJJNJ3HrrrVhYWABwYk7+8pe/xB133FGXOx8IBPCmN70JzzzzzKL15Uz46U9/il27don0JeCEh/rd7343JiYmcPDgwbrXv/Wtb4VWqxX/vuKKKyDL8qIc1iuuuALT09OoVCp1j690PjLMeoGNV6ZlIKOUjNjlWM7IHRgYOO1nTE5OQqVSLXrt8PDwiq+zt7d30WNOpxPxeFz8u1ar4XOf+xxGRkag1+vh8Xjg9Xqxd+9eJJPJFX9WI/IZiIOQ/uXGjRsXPbd582ZEIhFks9m6x0dGRur+PTQ0BJVKtSgvkq7jdEaNx+PBjTfeiEceeUQ89t3vfhcajaZOEeEjH/kIEokENmzYgIsuuggPPvhgnZLC6fi7v/s7PPbYY/jRj36Et771rUgmk3WFSgsLC0gkEiKXWvnzjne8AwAwPz8P4ESe7MaNG09Z6Dc5OYnOzs5F9yCFlRu1R1dyz3z605/G/v370dPTg127duGhhx6qM25XwkrmwHJQPu+2bdvO6vePHTuGAwcOLPp+N2zYAODk90tzUGnMA0vfp6diublgNptx00034aabbsIf/MEf4P3vfz/+4z/+A0eOHMEnP/lJACfuh1wut+zcqNVqZy2tBZz4Py733vS8ksb7w263AwB6enoWPV6r1RatISudjwyzXuCcV6ZlsNvtCAQCpzVa9u7di66uLthstrrHz9bjdKYsp0Cg3Ez//u//Hv/7f/9vvPOd78RHP/pRuFwuqFQqPPDAA6jVamf8mZQXGY/H0d3dfXYXfhYstxmS0eXxeE77Hm984xvxjne8A7t378bOnTvxyCOP4MYbb6z73Ve+8pU4fvw4fvzjH+OXv/wl/uVf/gWf+9zn8KUvfQnvete7TvsZF110kShmu+OOO5DL5fCnf/qnuOaaa9DT0yO+8ze/+c1429vetuR7KJUbVpuV3DNveMMbcO211+JHP/oRfvnLX+Izn/kMPvWpT+GHP/whbr311hV9zlJzYLkxrFarK3rPlVKr1XDRRRfhs5/97JLPNxpiZ4tyLqyUSy+9FHa7Hb/+9a9X5RpWm+Xuj5XcN8CZzUeGWQ+w55VpKW677TaMj4/jmWeeWfL53/zmN5iYmDjrooS+vj7UajWMj4/XPT46OnpW77cc3//+93HDDTfgX//1X/HGN74RN998M2666SYkEomzer9NmzYBwKLrPhUk3n7kyJFFzx0+fBgejwdms7nu8caw/ejoKGq12qLCLroO8iSdijvuuAM6nQ7f/e53sXv3bhw9ehRvfOMbF73O5XLhHe94B7797W9jenoa27dvX3G3pUY++clPolAo4OMf/zgAiKr2arUqvHKNPx0dHQBOeJuPHDmCcrm87Pv39fVhdnZ2UZTg8OHD4vmzIRAI4L777sOjjz6K8fFxuN1u8X8Azs6zRiHlxnuv0ftHntD9+/ef8v2Wu4ahoSHEYjHceOONS36/5ImkOdioBLHUfboUZzMXgBPGOnWm83q9MJlMy84NlUoljO1TfefLPdfX17fse9Pzq8mZzEeGWQ+w8cq0FA8++CCMRiP+x//4HyK3jYjFYnjPe94Dk8mEBx988Kze/5ZbbgGARe0Uv/CFL5zdBS+DWq1e5B353ve+h5mZmbN6v0svvRQ6nQ4vvPDCin8nEAhg586d+PrXv15nuOzfvx+//OUv8Yd/+IeLfucf/uEf6v5N30uj5+/FF1+EJEl1AvHL4XA4cMstt+CRRx7Bd77zHeh0Otxxxx11r2kca4vFguHh4SVlgVbC0NAQXv/61+NrX/sa5ubmoFar8frXvx4/+MEPljTOKBcSAF7/+tcjEokIGS8lNKZ/+Id/iGq1uug1n/vc5yBJ0oo9pUS1Wl0UCu7o6EBnZ2fdd2A2m8847YSMUqXXMZvN4utf/3rd626++WZYrVZ84hOfWCSPpryXl7uGN7zhDZiZmcE///M/L3oun8+LFBX6bj7/+c/XvWalXbu6urrQ09NzRnPhySefRCaTwY4dOwCcmJ8333wzfvzjH9elxITDYXzrW9/CNddcIyI7dMBb6uBpNpuXfPwP//AP8fvf/x6//e1vxWPZbBb/9E//hP7+fmzZsmXF174SzmQ+Msx6gNMGmJZiZGQEX//61/Enf/InuOiiixZ12IpEIvj2t7+9KF9upVx66aV4/etfj4cffhjRaFRIZR09ehTA6uWM3XbbbfjIRz6Cd7zjHbjqqquwb98+/Pu///spGyucCoPBgJtvvhmPP/44PvKRj6z49z7zmc/g1ltvxZVXXol7771XSGXZ7fYlvZrj4+O4/fbb8Qd/8Af47W9/K+SMaNMnHnvsMVx99dUihHs6/viP/xhvfvOb8cUvfhG33HILHA5H3fNbtmzB9ddfj0svvRQulwsvvPCCkI06Wx588EE88sgjePjhh/HJT34Sn/zkJ/Hkk0/iiiuuwJ/+6Z9iy5YtiMVieOmll/D4448Ljcy3vvWt+MY3voG//Mu/xO9//3tce+21yGazePzxx3Hffffhda97HV772tfihhtuwAc/+EFMTExgx44d+OUvf4kf//jHeOCBB874/kyn0+ju7sZdd92FHTt2wGKx4PHHH8fzzz+P//t//6943aWXXorvfve7+Mu//EtcfvnlsFgseO1rX3vK97755pvR29uLe++9Fw8++CDUajW+8pWvwOv1YmpqSrzOZrPhc5/7HN71rnfh8ssvF5rJe/bsQS6XE8buctfwlre8BY888gje85734Mknn8TVV1+NarWKw4cP45FHHsEvfvELXHbZZdi5cyfuuecefPGLX0QymcRVV12FJ5544oyiH6973evwox/9CLIsL5qzyWQS3/zmNwGc6LB35MgR/OM//qOQoiI+9rGP4bHHHsM111yD++67DxqNBl/+8pdRLBbrdKR37twJtVqNT33qU0gmk9Dr9XjVq16Fjo4OXHrppfjHf/xHfOxjH8Pw8DA6Ojrwqle9Cn/zN3+Db3/727j11ltx//33w+Vy4etf/zrGx8fxgx/8YNUbR5zpfGSYtqcJCgcMc1r27t0r33PPPXIgEJC1Wq3s9/vle+65Z0mpKJICWlhYWPY5JdlsVn7ve98ru1wu2WKxyHfccYd85MgRGYD8yU9+UrxuOamspSSdrrvuujrJnEKhIH/gAx+QA4GAbDQa5auvvlr+7W9/u+h1K5XKkmVZ/uEPfyhLkiRPTU0t+fxSUlmyfEJa6Oqrr5aNRqNss9nk1772tfLBgwfrXkPf08GDB+W77rpLtlqtstPplN/3vvctkk1KJBKyTqeT/+Vf/uW010ykUinZaDTKAORvfvObi57/2Mc+Ju/atUt2OByy0WiUN23aJH/84x+vkw9aCpIa+t73vrfk89dff71ss9nkRCIhy7Ish8Nh+b3vfa/c09Mj7qsbb7xR/qd/+qe638vlcvIHP/hBeWBgQLzurrvuqpNVSqfT8l/8xV/InZ2dslarlUdGRuTPfOYzdbJSsnxCKmspCay+vj75bW97myzLslwsFuUHH3xQ3rFjh2y1WmWz2Szv2LFD/uIXv1j3O5lMRn7Tm94kOxwOGYCQTTrd9/Diiy/KV1xxhazT6eTe3l75s5/97JL3tyzL8n/8x3/IV111lbhfdu3aJX/7298+7TXIsiyXSiX5U5/6lLx161ZZr9fLTqdTvvTSS+UPf/jDcjKZFK/L5/Py/fffL7vdbtlsNsuvfe1r5enp6RVJZcmyLL/00kuLpKhkebFUliRJssvlkm+//Xb5xRdfXPJ9brnlFtliscgmk0m+4YYb5GeffXbR6/75n/9ZHhwclNVqdZ1s1tzcnPya17xGtlqtMoC6uX38+HH5rrvukh0Oh2wwGORdu3bJ//Vf/1X3vsuNG43N888/X/f4Umvd2cxHhml3JFk+g/JlhlmnUK/0b37zm/iTP/mTZl/OklSrVWzZsgVveMMb8NGPfnRV3/uhhx7Chz/8YSwsLJy26OPhhx/Gpz/9aRw/fnzNiuQYppEbb7wRnZ2d+Ld/+7dmX0pT4fnIXIhwzitzwZHP5xc99vDDD0OlUuGVr3xlE65oZajVanzkIx/BP/zDP4jCk7WmXC7js5/9LP72b/+WN0qmqfz93/89vvvd7y4qPLuQ4PnIXKhwzitzwfHpT38aL774Im644QZoNBr87Gc/w89+9jO8+93vXjU5n/PFH//xH9eJ+681Wq22Lk+SYZrFFVdcgVKp1OzLaCo8H5kLFTZemQuOq666Co899hg++tGPIpPJoLe3Fw899JDoT84wDMMwTOvCOa8MwzAMwzBM28A5rwzDMAzDMEzbsOK0Ae6ZvDzrwXnN47s0PLbrl/UwtgCP73Ksh/HlsV2a9TC2AI/vcqxkfNnzyjAMwzAMw7QNbLwyDMMwDMMwbQMbrwzDMAzDMEzbwMYrwzAMwzAM0zaw8cowDMMwDMO0DdykgFkzVCrVoupK+rckSYuek2W57kf5GMMwDMMwFyZsvDJrgsViwdDQECwWCyRJglqthlarhdVqhU6ng9frhcfjEQasLMuYnZ1FKBRCsVhELBZDqVRCOBzG/Pw8G7AMwzAMc4HCxiuzJpjNZmzZsgV+vx9arRZqtRomkwldXV0wm83YtGkTNm7cCJXqRCZLtVrFSy+9hN27dyOVSmF8fBypVAq1Wg0LCwtsvDIMwzDMBQobr8x5xeVywel0wufzYXBwEB0dHdBoNFCr1TAYDPB6vTAajcIDS55XtVoNh8OBQCAAi8WCcrmMdDqN2dlZ6PV6VCoVVCoVNmLbHEmSxGGmo6MDXq8X5XIZsVgMxWIR2WwW2Wy22ZfJLIEkSVCpVLDZbLDZbNBqtbBYLNBoNKhUKiiXyygUCpifn0exWES1WkW1Wm32ZTMMsw5g45U5b6jValxyySW47rrr4PP5cPnll8PtdgsDVaVSQafTQaVSwWAwAKjvrNHb2wuPx4N8Po9wOIxMJoNcLofjx4+jUCggmUyiUqk05f/GrA5arRYulwsmkwl33XUX/uiP/giRSASPPfYYQqEQDh48iEOHDqFWqzX7UhkFkiRBp9NBo9Fg586duOyyy+DxeLBz5044HA5EIhFEo1FMT0/jRz/6EYLBIDKZDDKZTLMvnWGYdQAbr8x5gXJavV4vRkZG4PF40NvbC4fDsei1VITV6JUxGo0wmUwoFotQqVTIZrNwuVwwGo2o1WoixYBpX1QqFfR6PUwmE3p6enDJJZcgFArh0KFDKJfLMBqNzb5EZgnI66rRaOByuTAwMAC/34+dO3fC7XYjFAphfn4eOp0OdrsdkUgEhUKh2ZfNLIHSmaBWq1Gr1VCtVlclqqUswuUoWftBhdQqlaoupa+xkLoZsPHKrDpOpxOXXnopvF4vrrnmGmzevBlms1l4V4lKpYJMJoNyuYx4PI54PC5yYTUaDdxuN9xuNzQaDWw2G3Q6HXp7e7Ft2zZEIhHkcjmUSqUm/S+Z1UCr1cLtdsPpdMJisQA4sThms1lxbzCtB81To9GI3t5e7NixAzabTRw2rFYrVCoVcrkcBgcHAQDj4+NIJpNsxLQQWq0Wdrsder0eW7duxebNmzE/P49f/epXCIfD5/TeLpcLDocD1WoV6XQalUoFxWIRxWJxla6eOZ8YDAb4fD4YjUZs27YNW7duRSqVwv79+xGPxxEKhRAKhZpmxLLxyqw6TqcT1113HYaHh7FlyxZs3rx5SZmsSqWCZDKJfD6PsbExjI+PQ6fTwe12w2AwQJIkuFwuqNVqWK1WsVFu3boVwWAQo6OjSCaTTfpfMqsBee68Xi/MZjOAE/dFLpdDOp1GsVhkY6cFUalUMJlMMJvN6Ovrw/bt26HX68Uct1gssFgsKBaLGBwchCRJSCaTmJiY4PFsIejwaLVaccMNN+COO+7AoUOHsG/fvnMyXiVJgsPhwMDAAIrFIubm5pDP5wGAjdc2Qa/Xo6enBx6PB6973etw5513IhQK4ZFHHsHExAReeuklzM/Pr5qX/kxh45VZNbRarZC/8ng8wiChcAMVbcRiMcRiMRQKBSwsLCCfzyMYDCIYDEKr1SIWi4kirkAgIN5XkiRoNBrodDrxb6a9UavVMBqNsFgs0Gq1qNVqKJVKSCaTiMfjHGpuMSh8aDab0dXVBbfbDZfLBY1GUzcflfrNypAj01qQY8DpdMJsNkOv10Or1Z7z+0qSBKPRCKfTiWq1Co1Gg0KhgGAwiHQ6zQeYFoYcTSaTCd3d3QgEAvB4PNDr9TCbzeju7oYkSZiYmIBKpWpaPQIbr8yqQCdtt9uNwcFBbNu2DZs2bRIe1GKxiEgkgmw2i8cffxxPPPEE8vm8KLrKZDLIZrMiB1Kn0+Huu+8WXoGOjg5hxJpMJhgMBt4Q1wE6nQ4+nw9dXV0wmUwolUpIpVI4fPgw9u7di0KhwBtdC6HT6WAwGNDT04PXve516O/vx5YtW3gutilGoxEDAwMIBALw+XxC+eNcx1OSJPj9flx00UXQarXQaDSo1Wp44oknEAqFWHWihdFqtdDr9ejq6sKtt96KDRs2oLu7GyqVCi6XCzfffDMymQwikQieffZZyLKMWq225uv0mhivlPS7VBclYqkEYN602gutVguz2QyLxQK73S6Ks6gYK5vNIpVKYWpqCvv27UOhUEAmk0GlUkGpVEK5XBZVzHq9XhR56HQ6VKtV4W1dKgWBaT+oWYXJZILVahUSS4VCAalUColEotmXyPz/0JzT6XQiKtLd3Y2BgQE4HI5Tzkda99VqNdRqNQDUdcxjmodarYbFYoHD4YDRaFzVtVWn08Fms8FgMMBmswEA7HY7r90tjlqthk6ng8ViQWdnJ/r6+kRzIZ1Oh46ODtjtdthstqbuxefdeNVqtfD7/bBarXC5XOjp6YFarUaxWES5XBZWe6VSQSgUQiwWQ7VaFSHmfD7PocM2gW54k8kkNqlcLod8Po+5uTk8/vjjmJmZwe7duxGPx1Eul1EqlUR1K3AiV27Dhg1wuVzYsGGD0IHVarWQZRmxWAyjo6OYm5vj3Kk2xmw2i9Dz9u3bMTQ0BJVKhYMHD2JsbEzkxzHNx2w2Y2BgABaLBYFAAH6/H52dndiwYYNY25fbwPR6Pfx+P2q1GvL5PCRJEt51Sg/hvPW1h8aL6ggGBgbgdDpX1RBJp9OYm5uD0+lEIBCoSw8C0LRcSWZ5JEmC3W6Hz+dDIBCAw+GAxWKBXq8HAOTzeczMzCCVSmFubg7lcrlpcpXn3XjVaDTo6upCIBDA8PAwdu3aBYPBgFQqhVwuJwzXYrGIPXv2YHR0FMViUSxutVqNjdc2QafTwWQyCeNVlmXkcjlEIhGMjY3hJz/5CY4dO4ZUKrVs3pPZbMa2bdvQ2dmJkZERuN1u6HQ6qNVqkS87NjaGaDTKxmubIkkSzGYzvF4venp6sH37dmzYsAEHDhzAgQMHMD4+zsZrC0Hd8Xw+HzZs2ICRkRE4HA4MDw+f0nAFTqaFUE6sxWJBNpvFzMyMaD6RSqXYiGkCKpUKRqMR3d3dGBwchMvlWjXjVZZlZDIZzM3NifnudDpFM5pmhZqZU0PGa29vL7q6uuBwOGC1WsXz+Xwe4+Pjok17pVJpWgrImhivXq8X/f396OrqgtfrFXlThUJBeN1KpRIymQx0Op34e7lcRjQaRTweX7XroS4vlUoF6XQahUJBhK15Ip0bFO6dm5vDoUOHkMlkhATWxMSEUBYgj/tSaLVaOJ1OdHR0wGw2i02PFlVe9NYHWq0WRqMRBoNB5Nnl83lEIhHE43FuPtFC6PV6dHZ2ore3F4FAAC6XC2azeUVFkxqNBna7HcCJtVetViOXy0Gv1yObzaJYLCIWi4k1mJtRrA0GgwEGgwFWqxU2mw1WqxWVSgWRSASJRGJV5h8V6mk0GhgMBphMJuGI4Bzp1oNS8ux2O7q6utDR0SE8rkSlUkEsFsPCwgKy2ez61nk1Go3YtWsXrrvuOhE+0Gg0qFarYqEig+SKK64QxmQ+n0elUsHCwgJisdiqfUmUZ5nJZPD8889jfHwc6XQa4XCYN8xzQJZlhMNhJJNJTE5OYnZ2FhaLRaQN5HI5TE5OIpvNnnKDMplM2Lx5MzZt2oSuri5oNBpe6NYh5Hl1u90wmUzQarUIh8N46aWXhIYv0xo4nU7ceOONQg5Lr9dDrVYv2tiWwmQyYePGjcI4LZVKKBaLWFhYQC6Xw89//nOxJs/NzXGUbQ2QJAlut1ukfgwODqK/v190szt+/PiqtGTW6XQwm82w2Wyi9bPdbhc1DBw5ax0oJ12n02HDhg24+eab4fF4RK4ykc1mceDAAYyOjiIYDDb1sHnejVfqUe/3+4X2H4WUiUaJFfLEVioVeDwexGIxAFj2d5ZjqdfncjmRZzU5OYloNIpKpcJJ5KsACVDTwUOr1YoNi7Q7lwsx0KlPr9fDbreLlqGNHlfyuja7uwdz9lDiv8VigdFohFqthiRJyOfziEajq+b5Yc4N8pyZTCb4/X50d3cDWFmRlSzLYkOk5hMAhPKI2WxGLpcTeXUARJ48c36hdtzURIA0tIvFIubn50U9wrlCXRYp0kq1C1Tkw3tua0Ed1ux2Ozo7O2G32xfJplFDoUgkgnw+v749r/l8Hi+99BKq1SpsNhu8Xq9o9VkoFKDX62GxWKDRaGC1WmEymYQBo9FoxAZHVciVSgUGg6FOEFuJ0qgpl8uigl2v10Oj0aBcLqNQKIhkctIrm52d5W4+q0S1WkUmkxF5quRlX+6UJkkSenp60N3djeHhYXR2doo2sJIkiQmTy+UwOzuLYDCIbDbL49Wm0HhfccUV8Pl8AIBkMolIJIJQKMRj2wKoVCrRVYdyzyVJEkbpqZBlGZVKRaQHVSoVyLIs1m1a6w0GA6688ko4nU6MjY3hW9/61qp4/JiloU6FBoMBl19+OV75ylfC7XZDpVIhHo/j6NGjePbZZxGJRJDJZM7ps6jBzMjICPx+/4q89Ezz0Gq1Ih3I5/PB7/eLiBhw8sBKmuxjY2OrGhE/G8678VooFPDyyy9jZmYGTqcTnZ2dAID5+Xkkk0lYrVb4fD4YDAZ0dnaKVpHDw8Mwm82wWq0wm80olUoiF85qtZ6yUICqGAuFgtAOtdlsdROIwlQqlQqVSgUvv/zy+f4qLhjIeF0pkiShu7sbu3btQm9vrzBeyRNTKpWwsLCAZDKJ2dlZhEIhoVbBtB8qlQrd3d244oorYDKZIMsykskkotEowuEwCoUCj22TUalU2Lp1K+6++254PJ66Yp7TbVh04CwUCiI8TEavwWAQxisAvOIVr8CuXbvwwgsv4Be/+AUmJyfP+//tQoXqCaxWKy655BLcdtttItc8Fovh2LFj+O1vf4tCoXDO6RuUmjA8PAyn07moNTjTWmg0GjgcDjgcDqE00Oh1peJ5Ml6bHfk878ZrrVZDJpMRXk9axKLRKDKZDPL5PKrVKnQ6nUjet9vtKBQKMJlMcDgcsNlsQuS+VCrB6XQuqy1ImqKyLIv+6Hq9HiMjI/B4PHU6obVaDeVymSU7mgR1V9Lr9fD5fOjt7RWndJVKJbw2uVwOc3NzWFhYEAcYLtpqP6hDml6vh8lkgs1mg06ngyRJqFQq4ofnY/OgkLJer4fNZhPSdyvJOy+Xy0in0yiXy0ilUkilUkJJplariW5cOp0ODodDhJDpM91uN7xeL3K5HHtgzwNarVbkmZNBWSgUMDMzg0QiIfZXWl/PFrVaDY1GA6PRCLvdLlIFmdaFjFe32y0inkoKhQLy+byY362wPp9347VcLmNmZgbhcFgkBNPjVH1KVatUiWgwGES+hd/vh9vtRi6XQzAYRLFYhM/nQ0dHx5ILqizLKJfLqNVqSKVSiMVi8Hq9eNe73oVdu3ZBq9XCYDCI5yORCFKpFFe5NgGj0Yjh4WE4HA5ce+21eM1rXgOTyQS32y08AsViEbOzs/jVr36FiYkJHDhwQGgAt8IEYlYOVZ6bzWYEAgH09vYCABKJhFChoGYVPLbNwWAwoLe3FzabTRwmKa3rdLnm6XQaL730EmKxGKanpxEMBoUHVpZldHd3o7u7Gx0dHbjqqqvg8XjE71osFlx88cWwWCw4dOgQjhw5wvfAKmO323HllVdiYGAAGzduhMFgwNzcHP7rv/4LY2NjOHTokHAmne13TwcRo9Eo5DENBgN0Oh3XlrQwJpMJGzZsELKmynGSZRkLCwsIBoOYmJhomaLK8268yrJ8xpqNGo0GJpMJGo1GGJ/ZbBbT09MoFApCPms5zysZxmS8JpNJpFIpYSxT8U+pVEI+n2eZrCZBOVhOpxNerxednZ3iAEOecZJNC4VCmJ6eFuPI49V+UEEebW4mkwnValXkUlJuNI9t81DOSSrkofoCpeGqHCMas0KhgEgkgnA4jOnpaYyPjwvlGBpf+t3GNVer1cLj8SCdTmN2dnaRIg1z9lBxFEW4Ojs7RXckci5RDuO5rq0qlUp0SKTOeVqtVrwvz+3WhBwLyloTgmw4sqVapZh2TdrDnim1Wk3kNJKkChkxlUrltAL1tJiaTCZ0dXWJyjmDwSC0Y6PRKI4fP479+/cjEolwjl0T0Gq1cLvd8Pv9sNlsQhaLNsqFhQVMTk5ibGwM4+PjmJ6eRjKZ5AWwTdHpdPD7/XA6naJNZLVaRaFQQC6XE4U9zNpDqQLd3d248cYb0dPTg61bt4rwfmPYl7ofkpxhNBpFKBTCU089hVAoJPR6SVNblmUUi0Ukk0lkMhlceeWVIrqm1Wphs9lw2WWXYXBwUBSKpFIpjI+PCz1JvjfOHEmS4PF4RP7pyMgIBgYGYDQakUgkEI1GMTc3h1AotGzjmDPBYrFgx44d8Hg8Qp2CpNGy2ayoUmdN39aC2r729PTUyWNRE6mxsTH8+te/RjAYPOdivtWipY1XAOI0D5w87ReLRUSj0dO+z+DgIDo7O9HV1SWqLMlzGw6Hcfz4cRw+fBjFYrFlThMXEuRtCQQCsNlsdeLVZLwePnwY4+PjmJiYwPT0NG9gbQwtkH6/Xxiv5LGjtAGmOZDCS09PD2688UZs3LgRFosFZrNZ1AkooaLMQqGA48eP49ixY5iensbTTz8tlFuU6R+SJCGVSiEcDgv1EJ/PJwxVq9WKyy67DOVyGSqVCrVaTRjB+XyePfJnCRVObdq0CUNDQxgeHkZfXx9SqVSd8To3N7cq36/FYsH27dvR19eHnp4eIY02NzeHeDyOWCwmCjJ5PFsHnU6HQCCAnp6eurWZjNfx8XH85je/QSKRQDqdbvblAmhR41XJcjf4qW58MoIcDgf6+voQCASEZmgul0MoFMLc3BzS6bRIUOeJtHa4XC64XC74/X709PSgs7NTqEdUKhXReScUCmFqagpzc3OiYplpX0gzlDqnARDRlYWFBWQyGR7jJkEGKjUfMBqNi7rbASeUP6ipwOjoKFKpFMbGxjAxMYFwOIxcLif6nSs9a5TORUUf4XAYdrsdXq9XhCmp9sHtdqO/v1/cL3q9nj1154DZbIbH44HD4RANJnK5HMLhMGKx2KoYkmq1Gmq1WtQsdHR0wGQyAYDQj52fn0cikajT6maai06nE5rbNpttkSoTdUAledFTabWvNS1vvJ4ptOBptVps27YNd911F1wuF7q7uyFJEmZmZvD4449jbm6OQ1JNQK1W4/LLL8erXvUqeDweXHzxxSKMrFKpkE6nsW/fPsRiMTzxxBN48sknkclkkEgkmn3pzDmi0+lEbrPNZhPeuOeeew5jY2MtIb9yoUKGK9UbWCyWRTUFsiyL9ICpqSn84Ac/wNTUFJLJJBKJBEqlEpLJJEql0pKfQeHiYDCIZ555BhMTE7jyyitF8a1Go4FGo8G2bdvQ19eHQ4cOYc+ePaLtNHvmzxxJktDZ2YkdO3YgEAgI/fRgMIhnn30W09PTq+JJMxqNsFqt4rM2btwIl8sFWZaRSCTw7LPPCg89KYowzUWSJFFvMjQ0JDqtUY47NYsqFouIx+MIBoMoFArLzu+1Zl0ar6Qo4HK50NvbC7vdLk6B5HkNh8PIZrM8idYYlUolxKtdLpdI6aCcunK5jFgshnA4jLm5OczMzIgKdKZ9oW5LRqMRZrMZOp1O5EFGIhHMzc0hlUqx8dpElN7XxhxXCiHm83kkEgnMz8/j2LFjGB8fFy2gTwc1LCGvn0qlEkovJJkFnKiKt9vtiMfjMJlMooiTOXMkSYLRaBSeV5Iny2azCIfDq1bvodVqYTQaYbFY4HQ64XK5YDAYxByfn5/H7Ows0uk0e9BbCJLEs1qtogMqQUWWpBhCUZVWYd0ZrwaDARs2bIDX68Xg4CAsFovopUzhrng8jng8zr2V1xClfqTX663LQ6ZOXGTIvPzyy5iYmMD4+LjIR2ajpn2hcFR/fz+2bduGgYEBmM1mzM/PIxQKYXJyEuPj40gkEjzOLUipVEIqlUKhUMCLL76I5557DuFwuC5N4EwoFAqYmppCJpPByMgIQqEQjEYjnE7nImF05uygNAydTgeXy4XOzk5hvNZqNczOzuLFF19EIpFALpc7p89SqVQIBALYuHEjhoaG4HQ6YTQahSNiYWEB09PTmJyc5ILbFkKSJFitVgQCAXg8nkVzL5fL4dChQ6LzYauN27o1XgcGBtDf3y+MV6qMzWQyon96q7i/LwTI62YymeDxeNDZ2QmTySSM12KxiHw+L4zXQ4cOIRaLca5rmyNJEmw2Gzo7O9HX14ctW7ZgaGgI6XRaGK8TExOYnJxsqVM9cxLKSyYd15/85CfI5XKnVX1ZjkKhgOnpaUSjUUxPT2Nubk54fth4XR0oAqnX6+FwONDZ2VkX8QiFQti9e/eqrK+SJMHn82Hnzp3o7u4WxmsulxPGazAYxNTUFK/lLYbFYjml8Xr48GEEg0HMzs623NitG+OVJqrdboff70d3d7doMVqpVDA7O4tEIoGZmRnR2YsVBtYOvV6Pnp4ekWOj1+tFgYYsy2Khi8ViyGQywqPTahOGOXOo6YjFYhGC5RSCpnnI2r2thTLftVAoYGxsDPPz8wgGg8jlcqL169mibHigHHdlC1pSp+js7ESlUmHP/BlCqRi0N1KaRqlUEilzq/V96nQ6GI3GuhSPcrksUko4etZ6kPYvddGjcaMUIaVuM9UGtRLrxnilnrz9/f249tprsX37dtEZJhqN4uc//zl2794tZJe4f/ra4vF4cOedd2JoaAhbtmwRItlqtRq1Wg0zMzPYv38/xsbGMDMzg0gkwvnI6wDSmdy4cSP6+/vhcDhgMplQKBQQDocRjUZZOqfFaFQYmJubwze+8Q3s378fiUQC8XhcVCGvxucopbiUn0ud9wYHB/GrX/0KMzMzvCasEMox12q1os16oVDAkSNHEIvFVtWTJkmSiKi5XC6hJJJOp4XcGe+1rYndbhed9JTdT0ulEiKRCF588UUcPnx41aTUVpN1YbzSCcJms8HhcKCjo0O0OKOE8dnZWRw7dgzz8/PIZrM8mdYI2pzMZjN6e3sxPDwMj8cjZHjolJfJZDA/P49oNIpsNsspHesEmpsUFibPTK1WY69Mi9BYqLVUX/OJiQkcOXJkVT9P+bOUlqxWq4XL5UKlUoHZbF6Vz76QoLWXVCRkWRbKLavd4pM8r+ThpS5qNMf50NFa0HwzGAxCz5nmH2m7FgoFxGIxYTO12hq9LoxX4MQpfXBwED09PTCZTEJ2KR6PY2ZmBlNTU6I7BE+ktcPtdsPlcomGET6fT8jw0Okul8thz549+M1vfoNoNNoyHTyY1cFiscDn8wmvDDWgOHLkiGj5zDQPSZLg9/uxdetWDA8Pw2g01m1Uq71pUQqR2+0WXh/SlVWmEaTTabz88ssYGxvD5ORky22erQzp6lJXs3A4jGq1KqrJHQ4HjEYjSqXSWbdHp1awBoMBnZ2d2LRpk2gpDACpVArT09OiMQXTGlARn9lsxvDwMLZu3QqbzSbGrVKpoFgsigL3dDrdksXt68J4lSRJGK+dnZ2wWCxQqVTIZDKYnp5GMBjE9PQ0ZmdnuV/2GiJJElwul9CQ6+rqgs/nE7k1ys4r+/btwzPPPINCocDGzDqCvO4dHR3CeK3VaohEIjhy5Ajm5+d5vJuMJEkIBALYsWMHurq6hKwgsdo62AaDQTQn6enpgc/nE62h6XNkWUYqlcLLL78sdJ953V45tVpNdCtLJpOYn58XncwoB11prJyNQ0elUokiXFIb0Ol0QiM0mUxiZmYG8/PzHElrIXQ6HXw+HxwOh0jjU7aAJuWffD4vjNdWpO2NV3J/G41GOBwOoRkqyzLS6TSCwSBCoZAIXfDpfW2gEKTD4UB3dzd8Pp/o7qIMT+TzeWSzWSFg3tiZh2lPlDl3JGBuNBqFZF0qlUI0GkUymeTCyRaAOiPZ7Xaxfq4WlIqg0Wig1WrhcDgQCATQ1dUlmpMoUxVID7ZSqYgfXhPOHMpLps5mAGC1WqHT6eD3+7FhwwZkMhmEw+HTetao8IuaSdDcJiPY7XYvmXLCbX1bD5qLOp1OHBqVbdlpfc5kMi29Nre18apcEH0+HzZv3gyn0wmDwYByuYzjx4/jF7/4BRYWFhAKhXgirREki6XX67FlyxbccsstcLvdcDqddWLjpVJJyCVRz2vOf1wfqFQqmM1mGI1GdHZ2Ynh4GDqdDtlsFolEAqOjo6J70koE7pnzh0qlgs/nw7Zt2+rCh6sBtQzVaDSw2+1wOBwYGBjADTfcgIGBAXi93kUGD4UsqTaB1+0zR5ZlVCoV0cUuEonA5XKhp6cHFosFN954I7q6uhCJRPDcc88hGo2e8v1In9tgMMBqtcJqtUKv14v99qKLLqrLWT6faSfMuUHOPpJOUx4eKaXr8OHDorC9VVk3xitVVFqtVqhUKlSrVWQyGczMzCAajSKfz/MkWiNIIJsWt87OTnHiB06GIcvlMrLZrMip4U1q/UD5cHq9XrQbpc4+uVwO6XQaiUSCc+FaBIPBIDrtULX4akD3gU6ng81mg9vthtfrhd/vh9/vh8lkqts4gRNh7Hw+j2KxKNK8eF04c6gYliSrzGYz1Go19Ho9Ojo6IMsy7HY7JicnFxXLNUJ5rbTPOhwO6PV6uN1u6PV6uFwu8ZkEeX454tlaKBtYNM518rzG43GkUin2vJ4vzGYzNm7cCLfbjc2bNyMQCECtViMej2N+fh6Tk5MIBoNIpVLs3VlDLBYLtm7dCq/Xi02bNqGzsxNGoxFarVb0R49Go5iamsKzzz6LqakpTExMcGhwHWEymTAyMgK3243u7m5YrVahG5hIJJBOp3lDuwCwWCzYvn07PB4PhoeHMTw8DLfbLQwhEkavVqvI5/Mol8vYt28fDhw4gOnpaUxPTyOVSrVkwUg7UKvVMDExgWeeeUbIIXV0dECn08Hj8cBoNOLGG2887f6oUqlE2pfBYIDBYBApIJRLq/Sgy7KMubk57NmzB4lEgvffFsJoNKK/vx/d3d3weDxC9Ycksqanp7Fnzx7Mzs6ec/e180nbG6/btm1DT08PNm/eDL/fj1KpJLTlpqenMTMz05IyD+sZs9mMiy66CD09Pdi0aRMCgYA44ZHxeuzYMRw/fhzPPfecaAPLY7R+MBqNGB4eRldXF7q7u2E2m1EulxGNRhEOh9l4vUCwWCzYuXMn+vr6cOmll+KSSy4Ree9KY6dSqSCdTiOfz2P37t346U9/KpRiWrVgpB2QZRlTU1PI5/Po6uqCx+NBMpnE0NCQUH8ZGRlZlLrRSKVSQTabrfPEaTQaoaW+VK5rOBzG/v37USgUuGCrhdDr9ejv78fg4CDcbjckSUKtVkOpVEKhUEAwGGyLQ0dbGq8Gg0GELAKBAHp6emCz2QCcyKMMh8Mij5JDFmuPVquF1+tFV1eXSOMgZFlGPB7H+Pg4pqenRV4by5etL8hTQxJIdLqnQhz2sq9vKMedijXdbrcwdJYKUddqNRSLxbqUknQ63dJhy3aA9FYzmQxisRjGx8dF98JisQiNRiNadJ+KYrGISCQiPOCyLMNsNqOnpwdmsxkmk2lRrjTphfIe3BrQodFkMsHlcqGjowNms1kYr+R5zeVyomCrlffltjNeJUlCR0cHuru7MTg4iJtuugkjIyOiSCsajeJ3v/sdDhw4gLGxMc6pawI2mw2XX345duzYITppEdVqFfv378f3vvc9oT/IXtf1h0ajgc1mE33OAYiWg7R5MusTtVqNXbt24dWvfjUcDgeGh4dhtVrR0dGxrIeP1u54PI6pqSmMjo6iWCxyusAqkMlkhOB8OByGTqdDV1cXAoEADAYDnE6nqEdYjmQyiQMHDtRFTLq6unDHHXegu7sbQ0NDGBoaWqQaUS6XuQi3RTAYDDCZTPD5fLjooouwbds2kXNOkY9UKoW5uTlMTk6iWCy2tMe8LY1XknWhxP9AICDElvP5PBYWFjA7O4tkMsmTpglotVq43W74fL4lvSzJZBLT09OiR/r58sLRQtoYolwKKm7g+2V1kCRJdN1RpozQ6b6VT/RMfZtWkiNsfGwpaSQAQv1lZGQEVqtVVKkbDIZFrWcJ6riWTqeRTqdbXqannSC5sUKhgHQ6DUmShOqHwWCA1+s9rfGaSCREe2AilUrhiiuugMlkQmdnJ2RZXnJseU1tDcjLbjab4XA44HK56ool6bBI8pWtPv/axnglnTmdTodNmzbhhhtuQCAQgNPphCRJSCaTCIVCmJycxNTUFGZmZpDJZDg82YJoNBrRxadcLp/WC0fhJzIuaTEkw5h0B5ULp0ajETlZg4ODorNMI2S0ptNpHDp0CMlkErVaje+bc8RgMKCvr08U6EiShEKhgOnpaYyPjyMWi/Gm1mIoDVTlXDIYDOju7q4zXBwOB3bs2AGn0ymURZTvs23bNmzYsAE6nQ5msxkajQZ6vX5Z4zUej+OXv/wljh8/joMHD/L8O49Qm1hZlqHRaJBIJE6rNrBU85hqtSpSPBqfo+YkXq9XhKFb3Rhaz0iSBJvNhq6uLni9XjEXiUKhgKmpKSwsLJxWNq1VaCvj1WAwwGg0YuPGjbjppptgsVjgcDggSRISiYRoI0iFWrw5tiZarRYGgwHVarXOM7ccVIkMnBS9pk1QKclESJIk8qL9fj+uueYa+Hy+Re9LhrAsywgGg5ibmxMhbd48zw3qojQ0NASbzQZJklAsFjE9PY3R0VFEo1Geny3IUsYlGa/K4o3+/n7cc8896O/vh9lsFi2fCWpSAqzM8xaPx/HEE0/gpZde4sPjGpDJZJDNZs/odxrH8VTGK3CicNfj8SCVSiGXy7Hx2kSUxqvP51vkaSfjNRgMts3a3DbGq8lkQn9/v+jOYjabodfrRceeeDxe102rHb78CxHKWd62bRuKxeKKwhOlUgnRaFTkT1HbQ/Ku2mw2YSDRZ+h0Omi1Wng8HvT29godwkbIeK1Wq/D7/ahUKkgkEnVeJmblkKan2WwWBwhl95bVbjXKnDuyLCObzWJ+fh7lclloeBIGgwH9/f11RT0U9TKZTDAYDIs2Q6URfKrxzmazyGaziEajokkJszasxjxsnM+NChKkNMBzvvmQvqtSHYIOioVCQTRzahclmJY3XmkR7OzsxBvf+EYMDAxg48aN6OjoEI0IisUi9uzZg5/85CeIx+OIRCLNvmxmGdRqNa677jps2bJFGI2nmyjJZBIHDx5EKpUSrWTVajUsFgu0Wu2SxQLU8k6j0dQ1SGiEPntmZgapVApTU1N4+eWXsXfvXvb+nCGSJMHlcsHn86Gvrw8ul2vVRe+Z1UeWZUxOTuKZZ55Bd3c33G43TCaTeN7r9eKuu+4ShZWyLAthejqcLDeHT3VYIQ3SI0eO4NixY8hkMufl/8ecP5ZLAwFO5MTOzs62fOHPhYJerxddD8mhUCqVUCwWEQ6H8dxzz+HQoUOIRqNtsfe1/K5Ck4M8rxs2bIDP54NerxdffDqdRjQaRTAYFMYs01wol7RxYZMkCV6vF16vd8XvFY/HUalUEI/HRbiL8lkNBgM2bdqEzZs3r6goizbSxg01n8/D4/Egn88vmx/LnB5l+0jyfgMnv3s66bfDyf5CgXIgw+EwjEbjoupwvV6Pnp4e8Vrln8r3IBo9rkv9SZ2XEokEQqEQFhYWWIGizWlcf6lTGo9ra6BWq8WaTMYrSWTl83nEYjEsLCy0dGMCJS1vvBqNRhiNRlG97vf7RX5VKpXC888/j9nZWezfvx+xWIxDTy0AFebYbDZ4vV7RxeNsMRqNGBgYEE0oSqVSXccXpSG8nIGq7OxVKpWQTCbrvAHRaBRHjx7F/Pw8pwycJZIkwe12Y+PGjejr6xMSWRQaDoVCmJ6extTUFCuBtBCyLCMSieDo0aPCoHS5XNBqteLwcbZQSLJarSKXyyGXyyGRSGDv3r2IxWKiwDYajSKVSq3S/4hpBjyfWxdJkuB0OjEwMICuri4RiSwWi0ilUqJBCLVkbgda2niVJAlGo1HIOvh8Pvh8PmEIpdNpvPDCCzh8+DBGR0cRi8XYcG0BisUigsEgTCYTNBoNPB7POb0f5dwttTg2VkdTuKMxXFmr1bCwsIDR0VFkMhlMTk7WnTDT6TRGR0eRTCbZsDpLyHgdGRlBV1cXjEajkOWZn5/H3NwcpqenEQwG2yIsdaFAxmulUoFarUYikUAul6tr33q2kPFKOq7RaBTj4+P4zne+g8nJSSGNRbULTHuxXFoI57a3FmS89vf3C7UBWZZRKBSQSqWQSqVQKBTYeD1XJEkSxTh+vx/9/f3o6+sTOoH0ZQeDQczPzyMajSKbzfKG2CIUCgVMTk5ClmXRC/t0WqsajUYkk5vN5kUdXyh9hLw3JJ+lXCBJaJmKupThqlqtJqopc7kcQqFQ3WaZzWYRj8eRzWY57eQsIN1Pk8kEp9MJm80mcl3z+Tyi0SgSiYRQcuCNrbWgEG8qlcLk5CQ0Gg0cDgccDgd0Oh3sdvtZGbKlUgkLCwvIZrOYmZnB7OwsZmdnEYvFkMlkRFiZ74n2pHFdVxbAMq0DFWsZjcZFRbQ099otpasljVetVgubzQaLxYJXv/rVuOWWW+ByueD1elGtVnHw4EG88MILCAaDeP755xEMBs+r2D1zZiwsLOBHP/oRTCYTLr/8clx22WV10jmN0KnQ4XDAbrdj8+bNot2vElmWMT09jWPHjqFYLC4K/afTaRw+fBjJZBKJRALJZFI8p+zdTK0olfcLPVatVrk69gyhhVGn0yEQCGDLli1wOBwwGAyQZRmzs7PYvXs3JiYm+JDZouTzeZRKJRw/fhw//OEP4Xa7MTAwgIGBAXg8Hlx22WXLKnacikQigWeffRazs7PYu3cv9u/fL/LraA426jcz7QFJFWq12jpZNGoJywZsa6BWq0WNiNfrhd1uF44FynmlRhbtNGYtabySt446d2zcuFGcGGRZFh2a6ATPOYqtBaUNqFQqeDweeL3eJRsJEJIkic2zUqkgl8vBYDAseh3JWM3NzYkNUGm8xuNxHD58WChOtIvYcrtDXnGat3a7HRaLBSqVCtVqFdlsFpFIRHhemdaDjI1MJoOpqSkkEglR4FGr1ZDP5+tSshqlsMjwpIMJPVcoFDA/P49gMIiJiQkcPXqUDy/rBDq0arVaSJIk7gGKfPE4Nx9lWp1GoxEFW8o0O5r77XZ4bEnj1Wq1YsuWLXC73ejt7YXNZoNOp4NarUa1WkUymcTU1BQikQjnSbUw5Cmt1WrLGq6E2WyGyWSCyWTC008/LYp9Gt9vbm4O4XBYhDmVG2qhUBCGrVJQnTm/UMiJepmTd5vSO/bt24fnn38e8Xi8bSpZL1RKpRLm5uaEssfk5CQ8Hg9isRg8Hg/8fj/8fj+MRqNQfYlGoyINgHLJaa5Ho1Hs2bMH8/PziEQibbdBMstjMpkwMjKCjRs3wuPxoFQqIZ1O48iRI4hGo5iYmODxbjLKQ2UqlUIoFEKpVBItmwuFAmKxmMg7byda1njdtGkTOjs70dPTA7vdLnI0yHidnp5GMpnk/MQWhjpXzczMnPa1yhPiqYzcxvDiUpI9vGCuPWTAkvFaKBSQyWRQKBSwb98+vPDCCyJtg2ldSqUSwuEwAGB6ehqSJMFut2N6ehputxs7d+7Ejh074HK5RDODWCyG0dFRhMNh/Pd//zcikYiYw/l8HjMzM8jn80gkEjw31xFGoxEjIyPYtm0bNBqNaCZDKX2Tk5PsfW0BaE8k47VWq2F4eBiyLIt52Y7te1vKeNXr9dBqtXA4HPD7/QgEAovaDgL1nh6mtWFjcv2jLNKYn5/HoUOHAEAUv83Pz4uUEL4XWp9GqblisYhEIiGKHik1pFQqwWg0IhgMYnp6GrFYDOFwuC6Nq1QqIZfLoVgstt3myJwaUojI5/OikJYURahTE9N8yF6iIvdKpYL5+Xkhi5fJZESUrJ1oGeOV9Do9Hg+2bduGa665Bt3d3XA6neekEcowzPmnWq2iVqvhN7/5DQ4ePFj3GPU+54NMe1IoFDA+Pg6NRoPx8XE8/fTT0Gg0MBqNUKvVQmKnXC4jk8nUGankjafCLGb9UC6XEQqFYLFYcODAARw4cACxWAx79+4VKUI85s2HiugOHz6MRCKBrq4uAIDf78f09DRmZmYwNzfXdlHsljFeJUmCwWCAzWaDw+EQXZga23ryBsgwrQnphXJ75vUFFd0BqFPwYC5sqMAvkUhgZmYGx48fFx3T2OvaWlChe7VahSRJCIVCAIBYLIZkMtmWKjAtZby6XC709PTA5/PBaDSKIi0Aogo9n88jm81yGJJhGIZhmkQsFsNjjz0Gp9MpNHypSxPTepRKJWQyGczOzuLpp5+G1WpFJpNBOp1GLpcTB9R2oWWMV5VKBafTie7ubnR0dMBoNNaJYlerVaTTadHbnozXdjstMAzDMEy7E4vF8MQTT0CSpDpxe3YotSbUWj2bzYqiTKBekaCdaBnjlaBqc8pzJQHdRCKBo0ePIh6P153weKIwDMMwzNrDRdPtx3rpgNZyxqsSytOIx+M4evQo/v3f/x0TExNC65N0JRmGYRiGYZgLg5YyXkn+qlKpoFgsCq3IZDKJSCSC0dFRHD9+HPl8nvUiGYZhGIZhLkBaxnitVCoYGxtDLpfD4cOHcfToURiNRmSzWWSzWczPz4t0AdYLZBiGYRiGuTCR5BUmja6F1qoy35U6agEn5bGUSeGtRCte05nCWrpLw2O7flkPYwvw+C7HehhfHtulWQ9jC/D4LsdKxrdlPK9AvYbrekgoZhiGYRiGYVaXFXteGYZhGIZhGKbZqE7/EoZhGIZhGIZpDdh4ZRiGYRiGYdoGNl4ZhmEYhmGYtoGNV4ZhGIZhGKZtYOOVYRiGYRiGaRvYeGUYhmEYhmHaBjZeGYZhGIZhmLaBjVeGYRiGYRimbWDjlWEYhmEYhmkb2HhlGIZhGIZh2gY2XhmGYRiGYZi2gY1XhmEYhmEYpm1g45VhGIZhGIZpGzQrfaEkSefzOtoaWZabfQnnDI/v0vDYrl/Ww9gCPL7LsR7Gl8d2eXh81y8rGVv2vDIMwzAMwzBtAxuvDMMwDMMwTNvAxivDMAzDMAzTNqw455Vh1oql8oDWQ34TwzAMwzDnDhuvTEshSRLUanWdASvLMqrVKhuwDMMwDMOw8cqsHUqDlP7e+KdKpYJGo4EkSZBleckfgo1ZhmEYhrnwYOOVOS9IkiS8qBqNBmq1GgaDARqNBgaDAQaDATqdDjabDVqtFgaDASaTSRixsiyjWCyKn1gshlKphFwuh3w+j2q1imKxiGq1ilqthlqttsi4ZRhm7aA5L0kSVCoV1Gq1mI+yLIvnloqkKOcuz2GGYU4HG6/MeUGlUkGlUkGn04kfp9Mp/nQ4HDCZTOju7obJZILT6YTT6YQsy6hUKqjVakilUkin08hkMpiamkImk0EsFkM0GkW5XEYqlUK5XEalUkGlUoEsy8KIZRhmbVBGT1QqFSRJglarhVarBQDUajXxPBmv5XJ50YFTOXd5DjMMcyrYeGVWDdq8VCoVDAYD1Go1LBYLLBYL9Ho9HA6H+NNut8NoNMLpdMJoNMJut8NmswGA8KRqtVrodDoYjUYUCgWYzWaoVCrIsoxCoYBSqSQ2w0ql0uT/PXOmkGfeaDSKPGdJklCr1VAsFlGr1VCpVFCtVpt9qUwDNHY0141GIzQaDcxmMzQaDXQ6HTSaE9sLGa/VahXVahWVSgXZbBblchmlUkkYsqVSSRxACTZi1w/KQw6t2wRHzVoPlUoFvV4v/tTr9eLgSZHPpebsWsHGK3PO0GJE3ha9Xg+32w2DwYDe3l709PRAr9fDbrdDp9MJg1ar1cJsNkOr1cJqtcJisdTlvObzeeTzeRQKBXR1dSGfzyMYDGJmZgbJZBIAkEqlkEqlUCwWm/wtMGcCHXIsFguGhobqxj6fzyMUCiGfzyOTySCTyfDm1iKQZ5UOHVqtFn19fejt7YXNZsPw8DCsVquItlCKQK1WQyaTQTabRTabxcTEBLLZLBYWFhCJROoiKfR69sKuH5SFuJRGpkz3Uo45j3droNfrEQgEYDKZ0NnZiUAggFKphFgshkKhgJmZGYRCIVSrVZRKpTU3YNl4Zc6Zxjw3rVYLo9EoPKs+nw86nU4YryaTCWazWeTBqtVqmEwm8Xe9Xi+MYb1ej2KxKLythUIB+XweAGAwGFAoFMSiyItee0EbmcVigd1uh1arhUajQTabRSKRQLVaFQcZgI2YZqP0nKnVamGg2mw2+Hw+OJ1ODA4OwuFw1BmvlAaUTCaRTCaRSqWQzWah1+tRKBSQyWQAAGq1WuTCsuHaHBplCs/0+29ch5WFuEsZr8rx5jFvLWhftlgscLvd6O7uRrFYhFqtRjabRTKZrMtrX2vYeGXOCWWeGxmmlMNqNpths9lgMpmEIarRaFCr1YTRWavVoFKpxOlNGXqsVqvCmHW5XCKELEkSjEYjpqamUCqVkM1mm/01MGcIeV5VqhN9UmRZFocZSgFR5jDzhtZc6ICq0+lExKS3txdWqxXbtm3Dtm3bYLFY0NfXJ+a7VquFLMvCK0MbnVqtRmdnJ+x2u0gXyGazInpCaQTM6tPoaFCmelGNAqXu0MGDCmQb3wM46YlX7gPKv+v1erGGm81mSJIkDjPFYhGZTKbuvXm+Nx8aQ71eD7/fD7fbjc2bN2Pr1q0olUqIRCLI5XKo1WqYnZ0V6QPseWXaCuXiR/mpFosFDocDVqsVNptNhBd1Op0wWIvFojBaVSoVyuUy8vk8dDodAIhNUqPRQKvVwmQyic8jr47dbkc2m4VWq61bUJnWR7nB0WZFoehSqQSAN7JWQakiQFEVh8OBgYEBuFwuXHTRRdi5c6d4nPJd6aBaKpVEWLhcLguPbD6fR7FYRDabhU6nQyqVqjOauABz9aE5R3nJlKpDPyaTCSqVShiYpVJpyXoCpeFLuc8ajUbsBWq1WtQ8UKqY2+2GJEnIZDLC416pVMRhhYwf+pPHvTnQuOr1evh8PgQCAWzcuBEXX3wxKpUK4vE4stksgsEgXn75ZciyjFwut+bX2RLGa6PR0WiInKrj0lKhBr7p1xbleFEoiIoxcrkcMplMnSeVCjdoEaWJQkapRqNBuVyG0WgU70uLJclq6fV64d0h7x3TPihl1PR6PQwGg/DUkfFCGyjAc7pZ0NymeWo2m+FwOOB2u+H3++HxeESUheawVqsVxgsA8SfJ4anVahFlsdlssNvtAE4cWOlQC/CYryZKjygZlzSmJGFI3lEyXiuVCjQaTV1RHY2J8kCz3HtTagnNb4PBAEmShMQhvYYLMlsLZVqQzWaD0+mEyWQSBZg0zkrHQzNoivGqvOHp38rHlwppKPPelCdy5Z80CZba8HghXH2UWq60QVWrVRQKBcTjceRyOVQqFWG80sZFhi1w0hNAXlq9Xo90Oi2Kvjwej1hY1Wq1CD3l83lYLBYRogR4jJtJY0e0072WFkej0YiOjg6RFlIul1EsFlEoFJDNZsV9wqw9SsPVZDJBp9Ohr68PfX198Pv9uOaaa9DR0QGfz4eOjg4xpo0GDQBoNBo4nU4YDAYUi0VYrVaRJqDRaBCJRDA/Py+8fex1P3caJcwoOkZeVkrjcjgcwkPqcDig0WiEvnapVILD4UClUhFGrFKnl9Qj6HNobpNBbLFYhJqMy+UCcHJ/VnrhJUniLorniTPNY6YUP4fDgZGREQwODqKzsxN6vV78PjmoyBHVjHFbE+N1KS/qUvkxyj8bQxBKlJWJyklEMkpK43W5ZHCeJKuDcqNSLma0MWWzWTGelUpFeF2UeW00biqVCqVSCVqtFuVyGRaLBZVKRWyASqNH6d3hdIH2Q+mhIamlfD6PcrkMAHWeV56rzYUiHsrwr9vtFoar3W6HwWBY5GgATo5ztVoVKUEUWdFoNLBarbDb7SgWi8L7Tus9j/vqoNxbac2kdCyaf1Qwa7VaxWtUKpVwDNB4UfEszU0yZpWQR5U+g9ZrklqiiBl9htLTzgeW88/pipvpwKrX62Gz2eBwOGA0GsU9pFSJaOb6vOrGK92ISkNUeaNSmJc6LFGeFBXqKKvPVSpVXS4M/Vtp9ZMeJHlsgJO5cuS9oWIgOj1SAjoVAC3V8YVZGY3fHY1PMpmETqdDqVRCoVAQG6BKpUKxWBRGCt0nsVgMZrMZRqMRPp9P6EZaLBbUajVYrVYx/uQVIPUByo9j1p7G6Al5Uejvy0GHELPZDKfTCbfbjXg8jnK5LA4zPCebhzICZjAYROiwv78fW7ZsgcfjgcfjgdVqFWs2oZQ+aqwop6It8uR6PB7UajVoNBoEAgFhIMXjcR77VYaMV61WC4vFAo/HA4PBAI/HI9Q+yEhJJBJi/ChSRustPUbPK/dnZZoXpYVYrVZxSKnVaiKiQjnRPM/PH2fq2FHmtZtMJjgcDjH36RC6sLCAaDSKRCLRtGItYBWN10aPqjJp22g0inAFhRPImnc6nejt7YXRaITb7RZySnQCJC+d8jNIoF4pdl0sFoWEEk2oZDKJhYUFEbqm6kbaJOl9aKEF+LR/JtCGpNyoKPQryzI0Gk2dtBV9t8oiAPKc0umcGhJYLBZYrVZ4PJ66361UKigUCouMV+U1MWvDUkVyylP9qU74dLKnNcDtdqNUKiGdTtcdWrnqfO1pNEBI7cNms6G/vx/btm0Tc5Ny4ejAocxZpnWBDpfkZaMCTDJcKIzd1dUFjUaDWCwm7gGWwFs9Go3XQCAAs9kMr9cLh8MhxoeM00KhIDoYkvFKjh8yXhvl05TOK/LUO51OkdtMjg1Sm2nMbWdWj7ONSFJdidlsht1uF+k+NF4LCwuYm5tDPB4Xjqi2TRs4VfI2LVZKL6tWq4XT6RQ3tt/vF8+ReL2y6lGZUwPUG68UYqawk3IBper2arUqTnuJREIkjadSKUiSJCYoL5JnTmNKhjL/mDYsQtlpp1Kp1IWzKpWKyJfL5/NiogBYdBii91R64Dnc1BqsJM9ckiRhtNKcN5vNAIBcLodCocDemCajTN+i/DcaL4qQkNGqLNygHDgydJTzkgwn5R5B4WSj0Qir1Yp8Pi/eGwAbsKuEUi1Cr9fDZDLBarXCbDaLBjEUyaRIJTkISqWS+CGjlQ4mjfs+gLrDKxlBVLMAoM7JwZ7X1kKSJJhMJlGISc5GmoPlchnJZBKxWAzZbLb90waUuTFkWChza4xGI8xmMzo6OjA0NASj0Qi/3y+seurARFXky0E3P4WNyaNK3rh8Pl9XGUvhDtIVrVQqmJubw/Hjx5HNZjE1NYVoNCoMW86HPXOWyjGu1WrI5/NQqVQiZaDRyG08tVOelMlkEuLlZMQ25k7RvUYdmCh1hMdtbVnq+15JsZZKpYLf78dFF10En8+Hvr4+OBwOjI6OIhQKiRM9b2prj/JASUYO6Tx2dnZiZGQEXV1dwjujNDLJaK1UKpifn8fCwoJ4H5VKJRqXKLWcqcOeSqXCwMAAbDYb5ubmMDY2JtZ49sqdG8qDiM1mg8vlQiAQwMDAACwWC1wuF6xWK1KpFCYnJ5HJZBCLxbCwsIBsNotYLFZnwCpzHZWGKxk5BoMBwInoisvlQldXl/h8MoYzmYzonqic6zzfzz+nciqo1Wp0dXXh8ssvR09PD1wuF4xGozjQJJNJHD58GMePH0coFFqkQrGWnDfPa+NNTd5UOrm73W44HA4hu0LSRxqNpi4U3VjYBZw0jMlbSnlySrH8xjAWTZBarYZ4PA6dTodoNIpMJoNSqcRFP+dAo+dVmf/a+Jzy9QRVmpIBWywWodfrxfg3el3pfqDTOy98zedMvvtGzyt58oATnlcKTfJ4ri2N1enkKTUYDHC5XCKtiwxQZWc7MmjIO0cdeMhoJcNGGYlRtoI2GAywWCwolUoiH5aKcOma+H44O5T7J3m5qXOS1WqFyWQSBZNKxY98Pr/I60p7Lv0o1+NGLznVtphMprrHKC1BmVrSaADxWJ8fTmW40g/lQzudTuExp8MpGbCxWAz5fL79Pa/A4nCh8stQVhWSgUr5bWq1GqlUSsh2kNHZKFKtzLOhnvc0CehLLZVKdbp1NEFpAaXFuVarIZVKIZVKiY2Sjdezh8Zb6VGl73QlKg90n9BmRe1CnU6n0JCkFBBaLKvVqtCQZTml5nGmRisdZCnX3eFwiLzJQqEgDpRLCaMz5x/leu1wOOD3+9Hd3Y2+vj50dXXB5XKJAlxaM2ntzWQywnMXDAYxMzMj5O2oOIuKhMiQos/S6/Xwer0wGAzo7u5GT08P0um0iJzxYebsUKZn0CGkq6tLaPSaTCZIkiSKm2OxGJLJJBKJBFKplEgfUBqbwMl5r0wdIGeDVquFw+GA3W4XDirKm6X3aKw3WcrJwawuy+29wMkCWp1OB6/Xi8HBQXF/qNVqZDIZJJNJRKNRhMNhzM/Pi7bOzWJVjNdT3XDKUx8l5pN3jUTr0+m0SOCmkxmFiyjntVwuI51OiyKtxpagNClISFur1cLj8cBms4nEcRocvV6PZDKJmZkZJBIJFAoFFro/RxoPLysNKZOBqzzsWK1Wseg5nU5R8KfcMBuNV17wWhvlGkDGa09Pj9CBpDWBTvRsvDYH5Tg5HA5hSPb29qKrq0sU1CoP++RpjcfjGB0dRTQaxdTUFKanp4Ucll6vR09PD8rlssixlGVZOBrIuLVYLOjs7ERfX5/YKAuFQl16EnN6Gp1HOp1OKAt0dnbC6/XC4/FAp9Mhk8mItTQWiyGRSCAejyOVSonCLaWHtPFzgJMeVYp8ksQSGbCFQgG5XE44m5YzXpnV41TfqTLKQvcIpW76fD4MDg6K7pi0NsfjcZFOEg6Hmz5u51XnVVl5Wi6XkcvlEI/Hkc/nhQe2XC4vSuQnTyqlBNCpjUTvc7nconZkZOiSvBKpGzgcDvE85U+SEatsQUqfzZPo3DjTPEil4UoVqi6XCx0dHaJpAeXESZIkPPbKhZA3tfagsWDEYrGIIks6pCrl65i1o3ET0+l0cDqdCAQC6OjoEDKGtFYCJ1VdyHCljY1kdDKZjKhTKBQKsNlsojAok8mI1AS9Xi+88qQ043Q6hYg9hS2ZlaE0KMmYpAODzWaDzWYTHjWqDaFoJtUakGG5VFh/qbSvxnWcahNof20s7lM2N+Bak+ZC857SSSg3nXLVZVlGJpNBJBJBIpEQ0ZBmz8lVNV6VOTDASYOSjE7yltEGRouVUtMVONmlSZkOoCwGoFAGUK8vSULY5K3btGkT9Hq96N5EoROTyYRKpSJyfhKJBHteV4GzXXzovqHDxsUXX4zBwUEMDAzAbrcLbx0AJBIJjI2NYXp6Wnhdub1ge0AFIxaLBT6fD11dXQBOKgxQvmuzdAMvZMjw0Ol0cLvdsFgs2L59O66//no4nU50d3cLJRgKEVM4ORgMYnR0FAsLC3jxxRexsLCAdDotjFeKnORyOcRiMbhcLiG/Jcuy0BalYs3u7m7k83lYrVYcPnwYuVxOODLYwDk1jbUmlP4xNDQEu92OoaEhDAwMiOJoWZaRSqUwOzuLcDiMaDSKVCqFTCYj9mCld1T5/SudPWQAkRymxWIRKiLUFbFYLIpaFdrLG9+Xx3ftoIMGpVoGAgHYbDbhmafDaq1Ww8zMDJ5//nkEg0Gk0+mme12BVc55bcwbpZuSdFopR4JO2+RRo5M3fSF0EqQCADJQqKq8UftV+btarRaVSgUmkwnJZBL5fF4oGCzVXYR+lFIfzNpDp3Wj0QiXywWv1wur1SqK75QNCmhxVRZrMa0PedapaETphaM5zZ705qH0wJjNZrhcLvj9flHUQ+s0GS0UUVN6XqPRKGKxmNBgpteWSiXhsFCr1chmszAajWIdByCMLbPZDIfDIRqdKOXxmNOj9IBSkwllswA6hKhUKlFLQgdHksdqbP25VCqYstaBPleZGqRUh6GUL+W9wy2AWwPlvCf5NKPRKA6qJDW6sLAgNPJbYcxWLedVWWmoPP3RzV8sFkX3FOWpkIxJAHXdrpQacPQ4FXEpE8cbDU4ycGhi0kSiH8rrUHbj4knUXFQqFXp7ezE8PCxy61wul5DTUYaa5ufncfjwYczNzS3SkWRaE6UniIrxaJHMZDKYn58XIamlKo+Z8wulc1DUasuWLfB6vRgaGkJHR4do26qMqJVKJcTjceRyOczMzGB8fBzxeFw4DKjzDs3dRgH7eDwOjUYDj8cj9gyK2pESRTKZFDnRSkUYvjeWRmmwUpEypWF1dnaK/FPyhNIeWSwWkc1mRQSECqsai6aB+u++cb+nvdZqtcLn88HlconPozRAShdYLj2IU/fOP8q5Rs47s9mMnp4e+Hw+eDwekTJA0e/5+XkcOXIE8XgchUKh2f8FAKtsvBKNycDlcrnuNKiErH4AdeGJpSbMSirWyXilv9Pg0OJcq9Wg1+vrBoc3y+YiSRJ6enrwile8Aj6fD4FAQKgMaLVakX5CreloEpHxyrQ2ys2N2kSS8ZrP5zE/Py+KJ9mT3hxIj9vhcGDTpk3o6ekRFcfKnEVaL8l4pZDzxMQEMpkM0uk08vk8yuWy8Ko2Gp5kvGq1WlGMBZzcVElS0W63i/AzaXjzvbE0yj1X2dmSFAaUxit1NyPjtFQqLTJeGyUIG/depeFK/6Y0QEoLcjqdYgxzuZxwRi1nvC4VuWXOD0rjlcasp6cH3d3dwniVZVmkcYXDYRw7dkzcI63AqhmvROMC0+iJPVWytzJkuJRBudK8GKqu1Ov1MJvNMJlMIkwCoK6AjBZbNmDXHpLR0ev1cLvddV09KNQEnBhvKiTIZrNC4JrDy+0DefecTqcoAFKGpJLJJIrFInvS1xhlrqvJZILNZoPT6YTL5aor6qF1muQJi8WiqEpPJpPC+KEC28boGDkTGtNC6D2VeqEUjVNGXZTXy/dHPY3OIvo75Z8qGwApo5zUxIdSPBqLtE73eY2P0ZhR9y5SmaDPVHpzlb/HxdLNQWm8GgwG0aab5NOUndDoXmmVlAHgPOi8nuoktdy/yag8nVDxSr40CpdQ7+2enh6R76OUBUkmk5iensaRI0eQzWbFoLTKwFwImM1mbNq0CS6XCxdffDF27twJi8UCt9tdl3NDJ79UKoVgMIhQKCROhEzrQx41q9WKHTt2oL+/H11dXeJkHwwGMTExgVgsxvNvjaGxsdvt6O3tRX9/PzZt2oS+vj643W5RKEnjQuL1kUgEBw4cwNzcHI4cOYKZmRkUCgWhDao0gChnnQxbMlbVarUIJZPxSo4HyrEFIIxgZVEvcwKlsaqE9LJdLpf4sVgsQuFHqdoSi8UQDofFvxs7m51uT6dxI6O1o6MDg4ODQqubGpCQZ17ZkIbugeU+g9eD1UUZASdbyW63IxAIYNu2bRgeHobb7RbzNZvNihoT6kTaKnPwvEtlLfdvpce1sb3oqd7jdChzb0j6gTyvyuIBEmWmqspWGZALCfLEeTweuFyuOk1XClMCJw415CVXFhbwmLU+Ss8K5VR6vd660GU2mxWC9LxZrS00NtSDnpRZ7HY7DAZDXbifDE1aP6nTTiqVEg4ApYGqbAHduLYrPa3Kmgml55UiL0tdL98ni1EafTTfSPaMfpQFU6TaQy1aleoCK4lyNnp8lZ00qTCMCu4A1HXoavx9Zu1Qfu+kwERFktQOFoCwlein1WQMz6vxeiqUlYrnarACJz0IJIbd2dmJ7u5ukeNDJ7x4PI7Dhw+L3tsk49UqA7LeoY3SaDTC5/Nh48aNCAQC6OrqEt4WZcoAdUPbv38/ZmdnMTU1JSYSj1l7QOoiJpMJHR0dCAQC0Ov1IgIyNzcnJFiYtUNZ4GOxWOD3++H1eus0XQkyRlOpFObn5xEKhTA3N4eFhQWR8qE0XhuLeElpgvIuPR4POjo6YLVaReiSDFplIS+FvJUeOp73y6Ns60tpIKSlTPmmkiQhk8lgdnYW6XRayGNRcxBlBFR5+Gg0jpWfp9fr4fF44Ha74Xa7YbVahRHUmJ6gvD+WYqXpgcyZsZTXvKOjA1u2bEF/fz8cDoeIeAJAPp/H2NgY5ufnMT8/33KplU0zXoHVvTmVHgQqNujq6oLD4YBOpxOfl0gkcPToUdHijPNd1w4aIyrI8Pl8GBkZQV9fHzo7O4WHnPLsKPSYTqdx4MABoe9KYUkes/aAQsFmsxkdHR3w+/2QZVkYr6FQCLOzsyLnlVkblJ5OqhB3u9117ZgpMkZe8lQqVWe4Liws1LXZpkIfoN4IkWW5rvOSx+OB1+uFxWKpaxOrNFzJk0fPs+F6epQeULPZLIxX8qhSelw6nUYoFEIqlRItmckp0LgfLpcqAJw0Xkkf2O/3C51gg8EA4GSNCXl6l/LgLVUUxqw+jQcQv9+Pbdu2IRAICK8rjS8Zr1NTUy3RUauRphqvqwUtwOQCt9lsQmoJOFnwU6lUkEwmEYlEEI1GOUy5RijDSpRfR14eu90uPD2NxSHUspD05aiVb6tNIubU0JykDU2n04kWz42bJrO2NKYNmEwmEbJXRsbI4CBNV6W3VZkmsFRVOml/kni98pCq1P8ETtY90J/K9AJmMY0eUaXxr5TNohQsGk9lMY5y/ID6vMhGGutUSILSaDTW7bvK4miS41LeL0ovOq/la4syumE2m+HxeOBwOERRO90fpVIJsVgMkUikJZV92t54VS6+VqsVXq8XIyMj2Lp1K7xeL1QqVV3Bz5EjR7B7924hqn2qtnfMuUObj1arFTlQmzdvxvbt29HR0YGBgQG43W44HA4YDAax6FWrVUxOTuLYsWOYmprCoUOHMDU1hWQyycZrGyFJElwuF4aGhjA4OCi8MnNzczh27BjGx8dFkQ8br2sPbWJ2ux3d3d3o6OgQ/cyVOeeU9xYMBrFnzx6hNED556TL3aiZTSL5Wq0WXq+37h6gSnRlaBo46amrVqtio2Xj9dQoHQTU3pO+d2V7bTJaqd06FeEoPebkDCKU6RxKw3apFLDBwUF0dnaKlr5UUxKPx4UDgqTUlEYsc/5Rjh2l4/T09GDnzp0iR1mlUglViGQyif379+PQoUMIh8MtN07rxnil0x+dACl/A0CdHA8tuolEoi5M2WoDs55Q5kUZDAY4HA74/X5RAUt5NrQgKtMFKDc5mUwik8mwnFKbQRsc6T1S151SqYRkMol0Ot1yhQAXCsrNjNI6yHBtrPhWdmJKJBJIpVJ1LT6XMlyVaQnKwhDy7jYapY1pBo15l8zpIS83ebWVnm2lB1v5/SoL6xpfqzRUlZExpdOIiqKtVquoMSGvPaULKH+WajfLc3/tUHpeTSYTnE6nmJMA6g44ZC/lcrkzev+1GM+2Nl6ppahGo0F3dzc2b96MQCAAn88nThGZTAapVAovvPACxsbGcPjwYdEFplKpAOCJcz4grw1Vu9psNmzcuBF2ux2bNm3CwMCAEKyniVOr1ZDNZjExMYFUKoXdu3dj9+7diEajiEajYsx4vNoD2uBItNzlckGtVqNcLiMUCmHv3r0IhUJntDAyq0djfqTL5YLNZlvkDS2Xy4jH48hkMojFYkgkEsJjpyyeVIawlT3TPR6PyL+kdAEKJZMHThmipo1zKflCnvunRvn9099Je5U8sSSX1dfXh1QqhWq1Cp1OJxQkKpVKXc6xyWSqa/tKYeVarSYMVo/Hg+7ubpHHTGNcqVSEkkEulxNawGy4rj30PZOMGqXw0FgDJwzXWCyGhYUFEelUSomeyeecb9raeCWPq06nQ09PDy677DJ4PB5hvBaLRWQyGUQiEbz44ot48cUXRVcYCnPxxFl9Gj06JpMJLpcLmzZtQkdHhzBeySOnLKhLp9PYt28fQqGQMF7z+bzIUT7X0HJjiJI5f9A9QNqPLpdLFOKFQiHs27dPnOp5PNYWZZhZp9PBarXC5XKJ6n9lvisZr4lEArFYTEjWKVuJ0lqqNJ4oz9Xj8QiVASq+otAkKRRQmFqSJOGJV+Zh8v2xcpQeUuBk9zSTyVRXTU5ykdVqFWq1GrlcDjqdDuVyGQaDQeSnu1wuIbWl1WqFXFqtVhPySg6HA52dnfB4PKJ+gTx4SuNV6YDgnNe1RZnKQ0oUJE2pVPeJxWKiUEtpvLYabWu80sJLE9Jut4tJRHp2xWJR5LaS0K6y4IcnzepCiyVVn6pUKuFxoY4rNpsNJpNJLITkcSEB63A4LKqZKaeOi7TaD+UBhtqOWq1WUTVOOXessdw8aA0ljxp55ZTFPdQVizrckUf0VFXpSo8uOReU7wvU67wSjcVE9MP50CujMdyv9IbTOFNr9EqlIqrLM5kMACCbzUKv16NcLovX6fV6UcxD70GqE9VqVXh0KX+ZPO5K41l5HzWmDPCavvbQvkx5540Ffel0GnNzc4hEIkJPvRXHqS2NV2WyeGdnJ5xOJzZu3Iht27aJQZEkCZFIBPv370c4HMbk5CTC4fCyciDMuaHMiSI9R71eL6SRPB4PBgYGhEQOeXgoXHHkyBG89NJLWFhYwAsvvIBoNCpUISjfbrW8rszaQAaMz+fDpk2bhPA9cGKjnJ+fb9lT/XpGecg0Go0ihEjanEovDBmu8XgckUikLmVgKU1Q5YHFZDKJPFqlVit9Nv0AEIoGtVpNGMqkNpLL5VgZ5jQo84sp55VylZW5qQ6HAxaLRYT6q9Uqent7RSFVIpFApVIRzgWNRiPSAIhcLoepqSlh7Or1+rp8dlrXqQBM6YFVdu/iYunmoNfrRXql2+0Wh0s6ZIyNjeGpp54SKj+t1BJWSVsar8DJsJTFYoHdbhdhqcawSCwWQywWQzabFR1E2HBdXZQeF1o8qQqViufsdruoaKTcK6V2YzKZxMzMDCKRCMLhMOLxeF3XpdUeL04fOL8oiwIoMkKbIBkqFHbmMVh7lGkDSu+rMt8VONmcgETmG2WVlO8HnHQs0DqgNGaUxUO0fislmeg9Gz2vLKN2ehq92OR5VaZzUOEcGSs6nU58r3q9HqVSCWazGZVKpW7c6EBD467T6RCJRFCtVhd57BtVCYDFntdGryvP/7VBecChbnrkeVVK1WUyGbEHn6/9dzVoK+OVvmBqe+fxeHDxxReju7sbIyMjsNlsIlG8XC4jGo1ibGwMkUgEmUxmyUWXOTdosSL5Db1eD6vViqGhIdhsNgQCAQQCAdjtdnR0dIgUAp1Oh2KxiJmZGWSzWYyOjmJyclKoCtBB41Sfu5QndSmjdKlqW0IZwuJ7Y/XQaDRwOBxCR9Dj8QiVAapaLxaLHBJuInSwUHbUIoNSGXYGIJoF6HQ6IWRO4WZKE1JGX5TpQk6nU8gYBgIBURxGKV9K45X0nWOxmMiHJmklZnnIi63UV02lUqKoanx8HDabDeVyGcViURRTUetteowOGHQvUBiZuqtRK+DJyUlks1mRrkfGLVC/Jyi994lEQhRLkzQXG7Frh9JwHRwcxODgIPx+v5jztB4nEgksLCwgnU63rNcVaDPjFTjZs9lkMsHtdmPHjh3YsGED/H4/rFarkFgqFAqIRCIYGxsTXrzGycKsDsp0AQpNDQ4OiuI5v98Ps9ksWk9Srk0ul8PExASi0ShGR0cxMTGBfD4vJLFIDYJo9Cws5W1QQh6HRtkX5Ws5B+v8oNFoRESEjFdZPtGeOZvNig2zlRfH9QxtZBROprbMS8lXKQu7KKIiy7JQjlAar/QeJpNJpCI4nU5xePX7/UKeh3SdlYaPLMvI5/N1qgbk8WWWRunpJpF5Ml7z+TwMBgMmJiZE5IMMSqVsVbVarWtqQJ7yYrGIdDqNYrEoOuFls1nMzs4in8+js7NTFHNRioLS+0qpC6RkoDRel5JXY84fylbQIyMj2LhxI3w+X10RHnU9nJ+fR6FQaOlDY9sYr8owpN1uh8/nEx49yrcBThgjmUwG2WwWqVQK6XQamUyGBZHPE8pTNnnEKdeNfqhylTZISZLqdCPpsEGLKIBFm1ojyoW2UX8QwJKeVLpW5Umfqp2VfdiZc0er1cLhcIh2o2q1GoVCQeRRkVeI52TzUHrYyOPW2FVJmV5AYWflj1LYnqIvGo1GyOBROhelDVEOrDLUrPzMWq0mVGJIVomdDqdHqbVLRmSxWAQAEQbOZDLQ6/V1rXyVBiQ5hpRrOnniisWiaAlMhmixWKw7hCq7o9H7ZzKZOo1uZWtvdhisLUq5MzqEUs0JaeHTvGsH7e22MV7JONLpdNiyZQuuuOIK+Hw+bNiwQZwearUa8vk8JiYmEA6HcfToUYyPjwtPD+e6rh6NuWsajQY2mw0dHR3wer2i/avb7Ybb7YbJZBIFOzROyr72iURC5NdQzjJ5e8izoPSgktdIo9GIDZEMWQDCKFUulErvDi22dNBR5tXxgnrumM1mbN68GT09Peju7oZOp0M0GsWzzz6LiYkJHD16lHMZm0RjwRal8TR6XWk+kdZnuVwWRqlGo0E2mxXV55QLSXJYfr8fHR0dQtdZuTaQSgxtnABEDnSlUkEsFhMFtsooDM/JpaH1jLyn5P2uVCqii1Y4HIZGoxHtW5XrYeOaSuF+4GTdCHlgs9msyEmu1Wpi3aeUILqWfD6PVCqFiYkJjI2NYWJiQtSeNBqwzNpAtSiUtkP6y5IkoVAoYGpqSmi8kne8lcenbYxXZejK6XSiu7sbHo9HVMgCJydxMpkUuoTZbJbF7c8jylM6nejoh6qMKVeOirToZE4bFm1atHnSptYo9aKUYKFCAvLyWK3WumtR5lM2FplQAQrJ8JDXl9UIVgcaH6popgWyXC5jfn4eoVCozvPKNA9lNyZgceoNzSkq9KG5Xa1WhXIEzWfKdzcYDLDZbCJdwOFwiPxXk8lUVxFPKMPXhUJBGEpklPEhZ3mUhiitq2q1GqVSSXhPM5kM1Go1stmsGDelaD05IcgJQOslpXAUi0UhW6j8HZJPU0Y2ac0lz2sikRCHEB7P5qGMnFB9Cs3BarWKXC4nOue1Q2F7yxuvtHhaLBYMDg7Cbrdj69at2Lx5swhDUSct0gn9/e9/j9HRUYyPj/OpfQ1QGpXkrVGGGclwpY2R0gWq1SocDoeoQqUuW8pTPb0XbXrKwgSS36EQJIC6RbxWq6FcLoumFPQ5lUpFbIyVSgWZTEakE7A34Nyg+8BkMmFwcFB0VSNP+8zMDCYmJpBMJvl7bjJUrKMskCTDR5kvrtfrhbwdCdhTCkipVBJzklJFSNje7XbDaDTC7/eL3FplniulD1G+HTVAmJmZweTkpJBvYmPn9JDRWC6Xkc1mF2mtAif20nw+X1eUB6AuZUSp90n3A63VpPxAXloyfGiNph8yeildYWpqCtFotC1C0esVSTrZ6dDr9Yo8d2oaQ+kh5B1vh32w5Y1XmlAWiwXDw8Pw+XzYsmULtmzZUid8ncvlEA6HMTExgd///vfYt2+f6OzR6oPQjjRqOzb20aYFjbw1FP4HgFKpJDYlh8MhjFHyoNP7KL22jULZ5CmgyUcbXLFYFBsvABSLRZF8TpXupVIJyWRSeCRIS7YdJmyro6xo7e/vx4YNG0TryVQqhdnZWQSDQaRSKf6umwzluZHyBx0gleNC6Vper1d4Vz0eD4rFIhKJBMrlsoiwUJtZioZYLJY6qaXG/FbgZK5msVhENBpFPB4XBxzy9LHBc3po7SIvKLC0rnVj6pXSaCWDFYBYU5Xvrfx9StOjNC+l8UrRtGw2i7m5OUxPTyMSiXBXyyYiSRLMZjN8Ph88Ho9oFARAGK+pVArxeLxtmgK1tPFKp36DwSDaz/n9fmHISNLJVoILCwsYHR1FMBgUyeRcoXr+UYaKlC0dKWyvDNlTIQFwMp+OjFubzVa3MCoLwLRabV1TA1p06U9lfqskSXWFA/Q59FrldSuLHNizszooK9KVhxby3JRKJWEoMc2BDnfkqaOQMOUzKp0CjZqslCNLHlQStKf8czJUjUajCEsqDVdlUaQsyygWi8hms0gkEpibmxMGLMuonT1LGR005spUAWCxHqzyz+Va8yrfh9ZwpVKFUheY2sK2ev7khYDFYhFtupXOJEq3TCQSiEajyOVyTb7SldGyxisZJx6PBx0dHRgZGcENN9yA3t5eeDweIaqcSCSQy+Xw29/+Fj/+8Y+RSCQwOTkpPK48Yc4vdFqngwTlsaZSKWi1WlgsFjidTgBAPp8XIUnSi6Rkf4/HIzZJZV4cbZrKKliloDLpQlKPbioGyOVyYhLS+wKoazlJGySlD/C9cm5IkiQkkbq6ukTvbOqsQ6HEdDrNB8smo9ywyuUyQqEQpqenYbPZRK9zZSoQeea0Wi3sdrtI75FleVEuOoC6Q2Zj+Bo46dmLRqOYnZ3F7OwsnnrqKczOzmJqakp0euKivjNjJXseGZiNKQXK31vq70pjhxwNBoMBdrtdrOOkckBt2RcWFoRCAY9j81CpVOjr68PVV18Nn89Xp4lPCk0HDx7EsWPHEAqF2mKsWtJ4VVaym0wmkT/l9/sRCASEcQOcqIbMZDKYn58XygJkyDBrg1JuRakhqMw3Vm52tPhRIUitVhOeOuqatlSf7EYpLDKAyuWyMHKV495YpKW8PvLYL9cxiDk7dDodLBYLTCaTCCfSfUBFHKvR6pc5N5RhZrVaLSTrSD2AnicPnTK/kcKNyrkM1DcOafw7vZ7+VOZTUq7d3NxcnfIIz8vzg3IczuZ3lcasWq1epBFcrVZFyh4dXNnz2lyoDsHr9cLpdApFH4JqQ2KxGPL5fJOu8sxoSeNVp9MJFYGLL74Yu3btQiAQEGL3wIlcxkgkgueeew7BYBAHDhxAKpXikOQa0njKJ68rFedIkgSPxyPUHmixIwOGFAZIAo3CjMpQFr0v5b3Rhkbe02q1KpQlKpWK+CySwKJcHpJvoepYqqDNZDJ1klrM2SNJEmw2G7q7u+Hz+UR/83A4jMnJSYyNjYnQNH/XzYMMTqrsr9VqmJiYwAsvvAC/3y86JjW2jlT+qTRaG59b6vOAk/M4l8thdnYWmUwGe/fuxb59+xCNRjExMSGaWLRDtfOFCDkdaL2m9uxWq1V48qiWgPRCmeZCDiCDwVBX5E6HV8pPTiQSiMfjyOfzbTHvWs54pZxHEra+5JJLcOutt4oOTVT8USgUEI1G8bvf/Q6HDx9GMBis66LFrB1kxFYqFZHDRtXk6XQauVxO6EiSWL1ScoW8OXq9XnhsGnNpqXCDjF6lYsDCwgIikYjoEEIVt7R4ko5rqVQSPyS8Te/BG+W5Q2kDXV1d8Hq9QsQ+HA7j0KFDogiHv+fmQ95P6qIzOTkJtVqNgYEBXHTRRaIbk16vB7DYOF3KcD3VZ9H6QIfJ0dFRRKNR/Pa3v8UzzzyDYrGIWCwmPK7sgGhdyHglzVBqFEQSW+QUoHWX53vzUEaxyXg1mUxij6U9k5pJxOPxtjlwtIzxSvlS1EGrr68PLpcLHR0doksThbDy+bwINSUSCSGpwpOkuSg9o5Q2QJ3OKNSo0Wjq8lEJClsC9XJXtOkpjVfylJJxupTnVakfS94+Cl+Xy2WxaStDpHz/nD1KjwxJIgEQjSDm5uYQj8dbut3ghQjNWQrfRyIRTE9PQ5Zl+Hy+uq48ylSAlb4vGcjUfCAejyMej+P48eOIRqMIh8PI5/MiYsbzsLVRFvBRqhahTMdSppIxzYXmL9lXysJJqk9pRydOSxivSi1Xs9mMjRs34s4770RnZyc2bNgginmounV2dhZHjhzBxMQEjh07Jgq02umLX08ovaVK70qlUkEwGEStVoPBYIDb7YZOp0MqlRIi9ZQLpcxFpd9VthNUtjQk45W8NFQxDdSrHwAnUxmoQIVyXOlPpcA2c3YopdHsdjs6Ozvh8XhE55aJiQn87ne/QzKZRDabbfblMv8/5H2t1WpYWFhANptFJBKBWq2G1+vFrl27sGvXLqEGQnmNSrmrRmOW5hFJ0uXzeQSDQWQyGbz88svYvXs3EokExsfHkclk6tYCbuHdujRKbBmNRpjNZuh0OrEGFwoFaLVacVhpjII2FoUx5x8aK/pRaq7LsoxYLCZsKGW6TjvQEsYrAFFRTrJYvb296O7uFgaPUgKJFtloNCr6JrPh2nyUhVnFYlE0j0gkEkIHUq/XI5lMIplMLjJAacOjgg2l97TR89po+DZWxNJCSx5Xui4KiShDmXzfnDuNXdZoU6tUKkin04hEIuJ0z7QOdO/TPJMkCdPT08jlcujv70cmk6nrpqVs6bpUhTrNRYq85HI5xONxJJNJTE9P4+jRo0in05iamhItu+l3eB62NsoQtFJvm8ZuKc9royHEBuzaQhHtRq8rcGK+Un0KKcC0kx3VdOOV8h11Oh0GBgYwMDCA4eFhdHR0iD7ZAET4qVQqYXx8HL/73e+wsLCAdDrNValNhIxI6qtNoYhKpQKNRiP0G5USWI09ssmIVLaMJUNT+Tx5Z5XFW8tteo3e4KW0C5UeWubsUYaWgRNeNzJQ1Go1QqEQkskky+W0KMpDZzabxdTUFBYWFgAAs7OzsFqt6O7uhslkQkdHBzo6OqDRaERBF1WVU9etQqEgDFZ6v2w2i/HxcUxPT4vXKtME2mXDvBBRanOTIUT667VaTXT0yuVyogUtGUQkydWoOMGsPaVSSUS+dDodNBoNJicnsXv3boRCobbRdyWaarwqpTbMZjOGhoZwySWXoLOzEx0dHbDb7YuM13w+j/HxcTz//PNIp9NIpVKc3N9EKBxPfycFgVQqBUmSMD8/v0hah6r+lRtXoyHauMgt9fhK9AyXu2ZeRFcPKrLU6XTC61ar1YSxOjc3V9eil2k9yFuWzWYxPT0NSZIQDofx8ssvw2q1YsOGDbDb7di0aRM2bdoEvV4Pm80mDqjJZBKpVAqHDx9GIpFAKBTC7Oys6Hyo7HDH86/9UKYG0VzX6/UiEkrGq0ajEekg5M1bSueXWRuU3zu1ZZdlWaQATU1NYc+ePUIvv53mZdOMV7qhdTodzGYzLBYLHA4HnE6nWBSVskmlUgnxeBzpdFp80XR6Z5oLyeY0dtlSNhOgP2VZFrp/yudO5wVdzphdDg5PrR3KnOV0Oo1QKCTajJKUGYeGWx8aG2o6QgWXALCwsIB8Pg+j0SgOK6RIkE6nkclkkMlkRH5rJBIRnQ7JcG2nfDrmJMp8V2VES5nrqlarRe4yFVNTC+52CkWvN5TpHPF4HFNTU9Dr9YhEIgCAmZkZUbDVbrZUU4xXpbKAzWZDT08PnE4nNmzYgC1btoi+2FqtVnj24vE4du/ejXA4jKNHj2JhYUEU4DDNRxn6B+pDTY0ho+UMmdVc4HixXDtojsqyjNHRUREuzGQyqFQqiEajLFLeJtBmJ0kSstksCoUCkskkYrEY1Go1du/eLQxYqjZXdsIitQ/yspKBw0Zre9JotFKzCjJaU6kU5ufnYTAYhCTi2NgYDhw4gGw2i1gstmTxFrM2UASsUqng5ZdfRiKREI/XajVMTU0hGAzWFT23C03zvCqldWw2W90P5UYqcxZJB1B5oudim9ZiKYO03U5zzNlBRg/JnZFqBBXhsfHSXtB40vzN5/NLar0qIxycv7q+aZTFooJbZf6yRqNBNBoVnZrY89pcaB4DQCKRwNTUlKhLqVarSCQSyGQybVn/sebGK3niKG/G5XJhaGgIXq8XPp9PyG+Q0UqdOqanp3Hw4EHMzMxgfn6ew5AM0yLQXCVvHXnhKOeVDzDrg6UiJUspDjDrC2U6CaV7ZTIZFItFoT5BDQpkWUYkEqlTjGm3Kvb1hFIOjyQolUXM+Xy+7YxWYk2NV2XogWSTnE4nRkZG6oxXqlon700sFkMwGMThw4cxOTmJaDTKYQiGaRGUp3uqWGfWL2ysXjgox1ep2pLJZEReO+UzZzIZ0cGQjCKKkDa+F7M2KNdmKqKjx9udpqQNKAs8KHemUCggl8shn8/XvS4ajWJubk6IaNNJjmEYhmGY849SFUaSJJHHXCgUoNFoRCMZ6mCoLNyl32eaz3oahzU1XhtFrGu1GpLJJGZmZpDP52G1WoVWJBm2+/btw7FjxxAOhzEzM4N4PI5isbiuBoFhGIZhWhFloa2yE6IkSULknjx8Sj1tzoFmzidr7nldSo4lnU5Dq9WKVoGUMlAqlRCJRBAKhRCJREQCOOfQMQzDMMza0ViYB0Ds042vY4OVOd9I8grvstUWGaZWZQ6HAz09PTAYDPB6vbDb7XX6oMFgEAsLC0Lsmlp9tlKS8XqYqCwivTQ8tuuX9TC2AI/vcqyH8W2HsW28xrX63nl81y8rGdumGa+ESqWqk8WSZVn0SAdQ1yK0lQxWJTyJ1i88tuuX9TC2AI/vcqyH8eWxXR4e3/XLSsa2qe1hgfrOSpQno1KpAJwYWO7KwjAMwzAMwxAr9rwyDMMwDMMwTLNRNfsCGIZhGIZhGGalsPHKMAzDMAzDtA1svDIMwzAMwzBtAxuvDMMwDMMwTNvAxivDMAzDMAzTNrDxyjAMwzAMw7QNbLwyDMMwDMMwbQMbrwzDMAzDMEzbwMYrwzAMwzAM0zb8f58DZn7ub6DEAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 900x200 with 12 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def imshow(img):\n",
    "    img = img.view(-1, 28, 28)\n",
    "    plt.imshow(img.cpu().numpy()[0], cmap='gray')\n",
    "    plt.axis('off')\n",
    "\n",
    "# 获取测试样本\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    data, _ = next(iter(test_dataloader))\n",
    "    img = data.view(data.size(0), -1).to(device)\n",
    "    recon = model(img)\n",
    "    \n",
    "    # 显示原始图像与重建图像\n",
    "    plt.figure(figsize=(9, 2))\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 6, i + 1)\n",
    "        plt.imshow(data[i].view(28, 28).cpu().numpy(), cmap='gray')\n",
    "        plt.axis('off')\n",
    "\n",
    "        plt.subplot(2, 6, i + 7)\n",
    "        plt.imshow(recon[i].view(28, 28).cpu().numpy(), cmap='gray')\n",
    "        plt.axis('off')\n",
    "    plt.suptitle('Original (Top) vs Reconstructed (Bottom)')\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "38322e5e-beee-4cab-a4b2-35ade3ae8cd5",
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.save(model.state_dict(), 'autoencoder_mnist(2025.5.2).pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a911a0cb-9713-414a-93fb-2653591bfc12",
   "metadata": {},
   "source": [
    "# 信息熵"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e9016e58-fc80-45ca-bfd0-40a2274c01a5",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
      "Collecting scikit-learn\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.5 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.5/13.5 MB\u001b[0m \u001b[31m85.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: numpy>=1.19.5 in /environment/miniconda3/lib/python3.11/site-packages (from scikit-learn) (1.26.4)\n",
      "Collecting scipy>=1.6.0 (from scikit-learn)\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37.6 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m37.6/37.6 MB\u001b[0m \u001b[31m51.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hCollecting joblib>=1.2.0 (from scikit-learn)\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl (301 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m301.8/301.8 kB\u001b[0m \u001b[31m27.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting threadpoolctl>=3.1.0 (from scikit-learn)\n",
      "  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl (18 kB)\n",
      "Installing collected packages: threadpoolctl, scipy, joblib, scikit-learn\n",
      "Successfully installed joblib-1.4.2 scikit-learn-1.6.1 scipy-1.15.2 threadpoolctl-3.6.0\n"
     ]
    }
   ],
   "source": [
    "!pip install scikit-learn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "0b7ebfeb-2786-4bdb-b290-ec0a8cb6f232",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始输入 shape: torch.Size([256, 1, 28, 28])\n",
      "展平后 shape: torch.Size([256, 784])\n",
      "输出 shape: torch.Size([256, 784])\n",
      "compute_joint_entropy: {'encoder.0': 49.695736274891075, 'encoder.2': 49.69481051078412, 'encoder.4': 49.651658791737, 'encoder.6': 49.49682077294782, 'encoder.8': 9.386092689949045, 'decoder.0': 49.67335187659075, 'decoder.2': 49.677013459713535, 'decoder.4': 49.681383323248056, 'decoder.6': 49.69026090699949, 'decoder.8': 263.38126309165676}\n",
      "compute_average_marginal_entropy: {'encoder.0': 1.325592016955161, 'encoder.2': 1.193002839015522, 'encoder.4': 1.3801362104080213, 'encoder.6': 1.2651398007749532, 'encoder.8': 1.3909670600794286, 'decoder.0': 2.152672777321226, 'decoder.2': 1.4726440312036286, 'decoder.4': 1.2897706659110604, 'decoder.6': 1.3797734540873572, 'decoder.8': 3.3702175545654716}\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from torchvision import datasets, transforms\n",
    "import numpy as np\n",
    "from sklearn.neighbors import KernelDensity\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "# 数据加载器\n",
    "def load_data():\n",
    "    transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])\n",
    "    train_loader = torch.utils.data.DataLoader(\n",
    "        #datasets.MNIST('../data', train=True, download=True, transform=transform),\n",
    "        datasets.MNIST('./data', train=True, download=True, transform=transform),\n",
    "        batch_size=256, shuffle=False)\n",
    "    return train_loader\n",
    "\n",
    "def compute_average_marginal_entropy(outputs):\n",
    "    \"\"\"\n",
    "    对每维特征单独计算信息熵，并返回它们的平均值。\n",
    "    \n",
    "    参数:\n",
    "        outputs: shape = (n_samples, n_features)，例如某层的输出张量\n",
    "    \n",
    "    返回:\n",
    "        avg_entropy: 浮点数，所有维度的信息熵的平均值（单位：bit）\n",
    "    \"\"\"\n",
    "    # Step 1: 转换为 NumPy 数组\n",
    "    if isinstance(outputs, torch.Tensor):\n",
    "        outputs = outputs.detach().cpu().numpy()\n",
    "    \n",
    "    n_samples, n_features = outputs.shape\n",
    "    entropies = []\n",
    "\n",
    "    for i in range(n_features):\n",
    "        # 提取第 i 维的数据\n",
    "        data = outputs[:, i]\n",
    "\n",
    "        # Step 2: 使用直方图方法估计概率分布\n",
    "        hist, bin_edges = np.histogram(data, bins='auto', density=True)\n",
    "        hist += 1e-10  # 防止 log(0)\n",
    "        hist /= hist.sum()  # 归一化防止浮点误差\n",
    "\n",
    "        # Step 3: 计算信息熵\n",
    "        entropy = -np.sum(hist * np.log2(hist))\n",
    "        entropies.append(entropy)\n",
    "\n",
    "    # Step 4: 返回所有维度的平均熵\n",
    "    return np.mean(entropies)\n",
    "\n",
    "def compute_joint_entropy(outputs):\n",
    "    \"\"\"\n",
    "    使用多变量 KDE 估计联合信息熵 H(X)\n",
    "    \n",
    "    参数:\n",
    "        outputs: shape = (n_samples, n_features)，例如某层的输出张量\n",
    "    \n",
    "    返回:\n",
    "        joint_entropy: 浮点数，估计的联合信息熵（单位：bit）\n",
    "    \"\"\"\n",
    "    # Step 1: 将 PyTorch 张量转为 NumPy 数组\n",
    "    if isinstance(outputs, torch.Tensor):\n",
    "        outputs = outputs.detach().cpu().numpy()\n",
    "\n",
    "    # Step 2: 数据标准化\n",
    "    scaler = StandardScaler()\n",
    "    outputs_scaled = scaler.fit_transform(outputs)\n",
    "\n",
    "    # Step 3: 拟合 KDE 模型\n",
    "    kde = KernelDensity(bandwidth=0.5, kernel='gaussian')  # 调整 bandwidth\n",
    "    kde.fit(outputs_scaled)\n",
    "\n",
    "    # Step 4: 在训练样本上评估 log(p(x))\n",
    "    log_prob = kde.score_samples(outputs_scaled)  # shape = (n_samples, )\n",
    "\n",
    "    # Step 5: 计算平均负对数似然作为熵的估计（单位 nat）\n",
    "    entropy_nat = -np.mean(log_prob)\n",
    "\n",
    "    # Step 6: 如果你想用 bit 表示（即以 2 为底），则转换一下\n",
    "    entropy_bit = entropy_nat / np.log(2)\n",
    "\n",
    "    return entropy_bit\n",
    "\n",
    "# Hook函数\n",
    "def get_layer_outputs(layer_name):\n",
    "    def hook(model, input, output):\n",
    "        layer_outputs[layer_name] = output\n",
    "    return hook\n",
    "\n",
    "#model = SimpleCNN()\n",
    "train_loader = load_data()\n",
    "\n",
    "layer_outputs = {}\n",
    "# 注册hook以捕获中间层输出\n",
    "for name, layer in model.named_modules():\n",
    "    if isinstance(layer, (nn.Conv2d, nn.Linear)):\n",
    "        layer.register_forward_hook(get_layer_outputs(name))\n",
    "\n",
    "with torch.no_grad():\n",
    "    for data, target in train_loader:\n",
    "        data, target = data.cuda(), target.cuda()\n",
    "        \n",
    "        print(\"原始输入 shape:\", data.shape)  # 应该是 [64, 1, 28, 28]\n",
    "\n",
    "        # 展平输入以适配 autoencoder\n",
    "        data_flattened = data.view(data.size(0), -1)  # [64, 784]\n",
    "        print(\"展平后 shape:\", data_flattened.shape)\n",
    "\n",
    "        output = model(data_flattened)  # 现在不会报错了\n",
    "        print(\"输出 shape:\", output.shape)  # 应该也是 [64, 784]\n",
    "\n",
    "        break\n",
    "\n",
    "entropies = {name: compute_joint_entropy(output.cpu()) for name, output in layer_outputs.items()}\n",
    "print('compute_joint_entropy:',entropies)\n",
    "entropies = {name: compute_average_marginal_entropy(output.cpu()) for name, output in layer_outputs.items()}\n",
    "print('compute_average_marginal_entropy:',entropies)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "bda0bd00-f510-4d1c-93ca-eb56aae33825",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'encoder.0': 1.325592016955161,\n",
       " 'encoder.2': 1.193002839015522,\n",
       " 'encoder.4': 1.3801362104080213,\n",
       " 'encoder.6': 1.2651398007749532,\n",
       " 'encoder.8': 1.3909670600794286,\n",
       " 'decoder.0': 2.152672777321226,\n",
       " 'decoder.2': 1.4726440312036286,\n",
       " 'decoder.4': 1.2897706659110604,\n",
       " 'decoder.6': 1.3797734540873572,\n",
       " 'decoder.8': 3.3702175545654716}"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "compute_average_marginal_entropy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "d6841ccd-faf0-4555-a3e5-7e129873ac6b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'encoder.0': 49.695736274891075,\n",
       " 'encoder.2': 49.69481051078412,\n",
       " 'encoder.4': 49.651658791737,\n",
       " 'encoder.6': 49.49682077294782,\n",
       " 'encoder.8': 9.386092689949045,\n",
       " 'decoder.0': 49.67335187659075,\n",
       " 'decoder.2': 49.677013459713535,\n",
       " 'decoder.4': 49.681383323248056,\n",
       " 'decoder.6': 49.69026090699949,\n",
       " 'decoder.8': 263.38126309165676}"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "compute_joint_entropy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff9c68e3-07fe-4fad-b66c-5ad0c8fb8919",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
